¿Hay escenarios en los que la encuesta de eventos sería mejor que usar el patrón de observador ? Tengo miedo de usar encuestas y solo comenzaría a usarlas si alguien me da un buen escenario. Todo lo que puedo pensar es cómo el patrón de observación es mejor que el sondeo. Considere este escenario:
Estás programando un simulador de coche. El auto es un objeto. Tan pronto como el auto se enciende, desea reproducir un clip de sonido "vroom vroom".
Puede modelar esto de dos maneras:
Sondeo : sondee el objeto del automóvil cada segundo para ver si está encendido. Cuando está encendido, reproduce el clip de sonido.
Patrón de observador : haga que el automóvil sea el sujeto del patrón de observador. Haga que publique el evento "on" a todos los observadores cuando se encienda. Cree un nuevo objeto de sonido que escuche el automóvil. Haga que implemente la devolución de llamada "activada", que reproduce el clip de sonido.
En este caso, creo que gana el patrón de observador. En primer lugar, el sondeo requiere más procesador. En segundo lugar, el clip de sonido no se dispara inmediatamente cuando se enciende el automóvil. Puede haber una brecha de hasta 1 segundo debido al período de votación.
fuente
Respuestas:
Imagine que desea recibir notificaciones sobre cada ciclo del motor, por ejemplo, para mostrar una medición de RPM al conductor.
Patrón de observador: el motor publica un evento de "ciclo del motor" para todos los observadores para cada ciclo. Cree un oyente que cuente eventos y actualice la pantalla RPM.
Sondeo: la pantalla de RPM solicita al motor a intervalos regulares un contador de ciclo del motor y actualiza la pantalla de RPM en consecuencia.
En este caso, el patrón de observación probablemente perdería: el ciclo del motor es un proceso de alta frecuencia y alta prioridad, no desea retrasar o detener ese proceso solo para actualizar una pantalla. Tampoco desea anular el grupo de subprocesos con eventos de ciclo del motor.
PD: También uso el patrón de sondeo con frecuencia en la programación distribuida:
Patrón de observador: el proceso A envía un mensaje al proceso B que dice "cada vez que ocurre un evento E, envíe un mensaje al proceso A".
Patrón de sondeo: el proceso A envía regularmente un mensaje al proceso B que dice "si ocurrió el evento E desde la última vez que sondeé, envíeme un mensaje ahora".
El patrón de sondeo produce un poco más de carga de red. Pero el patrón de observación también tiene desventajas:
fuente
El sondeo es mejor si el proceso de sondeo es considerablemente más lento que las cosas que sondea. Si está escribiendo eventos en una base de datos, a menudo es mejor sondear a todos los productores de eventos, recopilar todos los eventos que ocurrieron desde la última encuesta y luego escribirlos en una sola transacción. Si trataste de escribir cada evento tal como ocurrió, es posible que no puedas mantenerte al día y eventualmente tendrás problemas cuando se llenen tus colas de entrada. También tiene más sentido en sistemas distribuidos débilmente acoplados, donde la latencia es alta o la configuración de la conexión y el corte son caros. Encuentro que los sistemas de votación son más fáciles de escribir y comprender, pero en la mayoría de las situaciones, los observadores o los consumidores impulsados por eventos parecen ofrecer un mejor rendimiento (en mi experiencia).
fuente
El sondeo es mucho más fácil para trabajar a través de una red cuando las conexiones pueden fallar, los servidores pueden fallar, etc. Recuerde que al final del día, un socket TCP necesita "mantener" los mensajes de "sondeo", de lo contrario el servidor asumirá que el cliente se ha ido.
El sondeo también es bueno cuando desea mantener una IU actualizada, pero los objetos subyacentes cambian muy rápido , no tiene sentido actualizar la IU más de unas pocas veces por segundo en la mayoría de las aplicaciones.
Siempre que el servidor pueda responder "sin cambios" a un costo muy bajo y no realice encuestas con demasiada frecuencia y no tenga miles de clientes encuestando, la encuesta funciona muy bien en la vida real.
Sin embargo, para los casos " en memoria ", uso de manera predeterminada el patrón de observador, ya que normalmente es el que menos trabaja.
fuente
Las encuestas tienen algunas desventajas, básicamente ya las mencionó en su pregunta.
Sin embargo, puede ser una mejor solución, cuando realmente desea desacoplar lo observable de cualquier observador. Pero a veces podría ser mejor usar un contenedor observable para el objeto que se observará en tales casos.
Solo usaría el sondeo cuando no se puede observar lo observable con las interacciones de objetos, que es el caso frecuente cuando se consultan bases de datos, por ejemplo, donde no puede haber devoluciones de llamada. Otro problema podría ser el subprocesamiento múltiple, donde a menudo es más seguro sondear y procesar mensajes en lugar de invocar objetos directamente, para evitar problemas de concurrencia.
fuente
Para un buen ejemplo de cuándo el sondeo toma el relevo de la notificación, mire las pilas de redes del sistema operativo.
Fue un gran problema para Linux cuando la pila de red habilitó NAPI, una API de red que permitió a los controladores cambiar de un modo de interrupción (notificación) a un modo de sondeo.
Con múltiples interfaces Gigabit Ethernet, las interrupciones a menudo sobrecargan la CPU, haciendo que el sistema funcione más lento de lo que debería. Con el sondeo, las tarjetas de red recolectan paquetes en memorias intermedias hasta que sondean o las tarjetas incluso escriben los paquetes en la memoria a través de DMA. Luego, cuando el sistema operativo está listo, sondea la tarjeta para todos sus datos y realiza el procesamiento TCP / IP estándar.
El modo de encuesta permite que la CPU recopile datos de Ethernet a su velocidad de procesamiento máxima sin una carga de interrupción inútil. El modo de interrupción permite que la CPU esté inactiva entre paquetes cuando el trabajo no está tan ocupado.
El secreto es cuándo cambiar de un modo a otro. Cada modo tiene ventajas y debe usarse en el lugar adecuado.
fuente
¡Me encantan las encuestas! ¿Yo? ¡Sí! ¿Yo? ¡Sí! ¿Yo? ¡Sí! ¿Lo sigo? ¡Sí! ¿Qué te parece ahora? ¡Sí!
Como otros han mencionado, puede ser increíblemente ineficiente si está votando solo para recuperar el mismo estado sin cambios una y otra vez. Tal es una receta para quemar los ciclos de la CPU y acortar significativamente la duración de la batería en los dispositivos móviles. Por supuesto, no es un desperdicio si recuperas un estado nuevo y significativo cada vez a un ritmo no más rápido de lo deseado.
Pero la razón principal por la que me encantan las encuestas es por su simplicidad y naturaleza predecible. Puede rastrear el código y ver fácilmente cuándo y dónde sucederán las cosas, y en qué hilo. Si, teóricamente, viviéramos en un mundo donde las encuestas eran un desperdicio insignificante (aunque la realidad está lejos de serlo), entonces creo que simplificaría el mantenimiento del código. Y ese es el beneficio de sondear y tirar, ya que veo si podríamos ignorar el rendimiento, aunque no deberíamos en este caso.
Cuando comencé a programar en la era de DOS, mis pequeños juegos giraron en torno a las encuestas. Copié un código de ensamblaje de un libro que apenas entendía relacionado con las interrupciones del teclado y lo hice almacenar un búfer de estados del teclado, en cuyo punto mi bucle principal siempre sondeaba. ¿La tecla arriba está abajo? No ¿La tecla arriba está abajo? No ¿Que tal ahora? No ¿Ahora? Sí. Bien, mueve al jugador.
Y si bien es increíblemente derrochador, descubrí que es mucho más fácil razonar en comparación con estos días de programación multitarea e impulsada por eventos. Sabía exactamente cuándo y dónde ocurrirían las cosas en todo momento y era más fácil mantener las velocidades de cuadros estables y predecibles sin contratiempos.
Entonces, desde entonces, siempre he tratado de encontrar una manera de obtener algunos de los beneficios y la previsibilidad de eso sin realmente quemar los ciclos de la CPU, como usar variables de condición para notificar a los subprocesos para despertar en qué punto pueden extraer el nuevo estado, hacer lo suyo, y volver a dormir esperando ser notificado nuevamente.
Y de alguna manera, encuentro que las colas de eventos son mucho más fáciles de trabajar, al menos, que los patrones de observación, a pesar de que todavía no hacen que sea tan fácil predecir hacia dónde terminarás o qué pasará. Al menos centralizan el flujo de control de manejo de eventos en algunas áreas clave del sistema y siempre manejan esos eventos en el mismo hilo en lugar de rebotar de una función a un lugar completamente remoto e inesperado fuera de un hilo central de manejo de eventos. Por lo tanto, la dicotomía no siempre tiene que ser entre observadores y encuestas. Las colas de eventos son una especie de término medio allí.
Pero sí, de alguna manera me resulta mucho más fácil razonar acerca de sistemas que hacen cosas que son analógicamente más cercanas al tipo de flujos de control predecibles que solía tener cuando hacía encuestas hace años, mientras que simplemente contrarrestaba la tendencia a que el trabajo ocurriera en veces cuando no se han producido cambios de estado. Por lo tanto, existe ese beneficio si puede hacerlo de una manera que no esté quemando innecesariamente los ciclos de la CPU, como con las variables de condición.
Bucles homogéneos
Muy bien, recibí un gran comentario
Josh Caswell
que señaló algo tonto en mi respuesta:Técnicamente, la variable de condición en sí misma está aplicando el patrón de observador para despertar / notificar subprocesos, por lo que llamar a ese "sondeo" probablemente sería increíblemente engañoso. Pero creo que proporciona un beneficio similar que encontré en las encuestas de los días de DOS (solo en términos de flujo de control y previsibilidad). Trataré de explicarlo mejor.
Lo que me pareció atractivo en aquellos días era que podías mirar una sección de código o rastrearla y decir: "Está bien, toda esta sección está dedicada a manejar eventos de teclado. Nada más va a suceder en esta sección de código . Y sé exactamente lo que sucederá antes, y sé exactamente lo que sucederá después (física y renderizado, por ejemplo) ". El sondeo de los estados del teclado le proporcionó ese tipo de centralización del flujo de control en cuanto a manejar lo que debería ocurrir en respuesta a este evento externo. No respondimos a este evento externo de inmediato. Respondimos a nuestra conveniencia.
Cuando usamos un sistema basado en un patrón Observador, a menudo perdemos esos beneficios. Se puede cambiar el tamaño de un control que desencadena un evento de cambio de tamaño. Cuando lo rastreamos, encontramos que estamos dentro de un control exótico que hace muchas cosas personalizadas en su cambio de tamaño, lo que desencadena más eventos. Terminamos completamente sorprendidos al rastrear todos estos eventos en cascada en cuanto a dónde terminamos en el sistema. Además, podríamos encontrar que todo esto ni siquiera ocurre consistentemente en ningún subproceso dado porque el subproceso A puede cambiar el tamaño de un control aquí, mientras que el subproceso B también cambia el tamaño de un control más adelante. Así que siempre encontré esto muy difícil de razonar dado lo difícil que es predecir dónde sucede todo y qué sucederá.
La cola de eventos es un poco más simple para mí razonar porque simplifica dónde ocurren todas estas cosas, al menos a nivel de hilo. Sin embargo, podrían estar sucediendo muchas cosas dispares. Una cola de eventos podría contener una mezcla ecléctica de eventos para procesar, y cada uno aún podría sorprendernos en cuanto a la cascada de eventos que ocurrieron, el orden en que se procesaron y cómo terminamos rebotando en todo el lugar en la base de código .
Lo que considero "más cercano" al sondeo no usaría una cola de eventos, sino que diferiría un tipo de procesamiento muy homogéneo. A
PaintSystem
podría ser alertado a través de una variable de condición de que hay trabajo de pintura que hacer para volver a pintar ciertas celdas de una ventana, en cuyo punto realiza un simple bucle secuencial a través de las celdas y vuelve a pintar todo lo que está dentro de él en el orden z correcto. Puede haber un nivel de llamada indirecta / despacho dinámico aquí para activar los eventos de pintura en cada widget que reside en una celda que necesita ser repintado, pero eso es todo, solo una capa de llamadas indirectas. La variable de condición usa el patrón de observador para alertarPaintSystem
que tiene trabajo que hacer, pero no especifica nada más que eso, y elPaintSystem
está dedicado a una tarea uniforme y muy homogénea en ese momento. Cuando estamos depurando y rastreando elPaintSystem's
código, sabemos que no sucederá nada más que pintar.Por lo tanto, se trata principalmente de llevar el sistema a donde tiene estas cosas realizando bucles homogéneos sobre datos aplicando una responsabilidad muy singular sobre él en lugar de bucles no homogéneos sobre tipos dispares de datos que realizan numerosas responsabilidades como podríamos obtener con el procesamiento de la cola de eventos.
Nuestro objetivo es este tipo de cosas:
Opuesto a:
Etcétera. Y no tiene que ser un hilo por tarea. Un subproceso puede aplicar la lógica de diseños (cambio de tamaño / reposicionamiento) para los controles de la GUI y volver a pintarlos, pero puede que no maneje los clics del teclado o del mouse. Por lo tanto, podría ver esto como simplemente mejorar la homogeneidad de una cola de eventos. Pero tampoco tenemos que usar una cola de eventos e intercalar las funciones de cambio de tamaño y pintura. Podemos hacer como:
Entonces, el enfoque anterior solo usa una variable de condición para notificar al hilo cuando hay trabajo que hacer, pero no intercala diferentes tipos de eventos (cambiar el tamaño en un bucle, pintar en otro bucle, no una mezcla de ambos) y no Tómese la molestia de comunicar cuál es exactamente el trabajo que debe hacerse (el hilo "lo descubre" al despertarse al observar los estados de todo el sistema de la ECS). Cada ciclo que realiza es entonces de naturaleza muy homogénea, lo que facilita razonar sobre el orden en que sucede todo.
No estoy seguro de cómo llamar a este tipo de enfoque. No he visto a otros motores GUI hacer esto y es una especie de enfoque exótico mío. Pero antes, cuando traté de implementar marcos de GUI multiproceso utilizando observadores o colas de eventos, tuve una tremenda dificultad para depurarlo y también encontré algunas oscuras condiciones de carrera y puntos muertos que no era lo suficientemente inteligente como para arreglarlo de una manera que me hizo sentir confiado sobre la solución (algunas personas pueden hacer esto pero no soy lo suficientemente inteligente). Mi primer diseño de iteración acaba de llamar una ranura directamente a través de una señal y algunas ranuras generarían otros hilos para hacer un trabajo asincrónico, y eso fue lo más difícil de razonar y me tropecé con las condiciones de carrera y los puntos muertos. La segunda iteración usó una cola de eventos y fue un poco más fácil razonar sobre esto. pero no es lo suficientemente fácil para mi cerebro hacerlo sin toparse con el oscuro punto muerto y las condiciones de carrera. La tercera y última iteración utilizó el enfoque descrito anteriormente, y finalmente eso me permitió crear un marco de GUI multiproceso que incluso un tonto tonto como yo podría implementar correctamente.
Luego, este tipo de diseño de GUI multiproceso final me permitió pensar en algo más que era mucho más fácil de razonar y evitar ese tipo de errores que solía cometer, y una de las razones por las que me resultó mucho más fácil razonar Lo menos se debe a estos bucles homogéneos y a cómo se parecían un poco al flujo de control similar a cuando estaba encuestando en los días de DOS (aunque no es realmente una encuesta y solo realiza trabajo cuando hay trabajo por hacer). La idea era alejarse lo más posible del modelo de manejo de eventos, lo que implica bucles no homogéneos, efectos secundarios no homogéneos, flujos de control no homogéneos y trabajar cada vez más hacia bucles homogéneos que funcionen uniformemente en datos homogéneos y aislando y unificando los efectos secundarios de manera que sea más fácil concentrarse en "qué"
fuente
LayoutSystem
que normalmente está durmiendo, pero cuando el usuario cambia el tamaño de un control, usaría una variable de condición para activar elLayoutSystem
. Luego,LayoutSystem
cambia el tamaño de todos los controles necesarios y vuelve a dormir. En el proceso, las regiones rectangulares en las que residen los widgets se marcan como que necesitan actualizaciones, momento en el cual sePaintSystem
despierta y atraviesa esas regiones rectangulares, repintando las que necesitan ser redibujadas en un bucle secuencial plano.Le doy una visión general más sobre la forma conceptual de pensar sobre el patrón del observador. Piensa en un escenario como suscribirte a un canal de youtube. Hay un número de usuarios que se suscriben al canal y una vez que haya alguna actualización en el canal que comprenda muchos videos, se notificará al suscriptor que hay un cambio en este canal en particular. Por lo tanto, concluimos que si el canal es SUJETO y tiene la capacidad de suscribirse, cancelar la suscripción y notificar a todos los OBSERVADORES que están registrados en el canal.
fuente