¿Cuándo sería mejor sondear los eventos que usar un patrón de observación?

41

¿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.

JoJo
fuente
No puedo pensar en casi ningún escenario. El patrón de observador es lo que realmente se asigna al mundo real y a la vida real. Por lo tanto, creo que ningún escenario justificaría no usarlo.
Saeed Neamati
¿Estás hablando de eventos de interfaz de usuario o eventos en general?
Bryan Oakley
3
Su ejemplo no elimina el problema de sondeo / observación. Simplemente lo has pasado a un nivel inferior. Su programa aún necesita determinar si el automóvil está encendido o no por algún mecanismo.
Dunk

Respuestas:

55

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:

  • Si el proceso A falla, nunca se dará de baja, y el proceso B intentará enviarle notificaciones por toda la eternidad, a menos que pueda detectar de manera confiable las fallas del proceso remoto (no es algo fácil de hacer)
  • Si el evento E es muy frecuente y / o las notificaciones llevan muchos datos, entonces el proceso A podría recibir más notificaciones de eventos de las que puede manejar. Con el patrón de sondeo, puede limitar el sondeo.
  • En el patrón de observación, una carga alta puede causar "ondas" en todo el sistema. Si usa tomas de bloqueo, estas ondas pueden ir en ambos sentidos.
nikie
fuente
1
Buen punto. A veces es mejor sondear por el desempeño.
Halcón
1
El número esperado de observadores también es una consideración. Cuando esperas un gran número de observadores, actualizarlos a todos de lo observado puede convertirse en un cuello de botella en el rendimiento. Es mucho más fácil simplemente escribir un valor en alguna parte y hacer que los "observadores" verifiquen ese valor cuando lo necesiten.
Marjan Venema
1
"a menos que pueda detectar de manera confiable fallas de procesos remotos (no es algo fácil de hacer)" ... excepto por sondeo; P. Por lo tanto, el mejor diseño es minimizar la respuesta "nada ha cambiado" tanto como sea posible. +1, buena respuesta.
pdr
2
@ Jojo: Podría, sí, pero luego está poniendo la política que debería pertenecer a la pantalla en el contador de RPM. Quizás el usuario ocasionalmente quiera tener una pantalla RPM altamente precisa.
Zan Lynx
2
@JoJo: Publicar cada evento número 100 es un truco. Solo funciona bien si la frecuencia del evento siempre está en el rango correcto, si el procesamiento del evento no toma demasiado tiempo para el motor, si todos los suscriptores necesitan una precisión comparable. Y se necesita una operación de módulo por RPM, que (suponiendo unos pocos miles de RPM) es mucho más trabajo para la CPU que unas pocas operaciones de sondeo por segundo.
nikie
7

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).

TMN
fuente
7

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.

Ian
fuente
5

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.

Halcón
fuente
No estoy muy seguro de por qué crees que las encuestas son más seguras para subprocesos múltiples. En la mayoría de los casos, este no será el caso. Cuando el controlador de encuestas recibe una solicitud de encuesta, tenía que averiguar el estado del objeto sondeado, si el objeto está en el medio de la actualización, entonces no es seguro para el controlador de encuestas. En el escenario de escucha, solo recibe notificaciones si el pulsador está en estado constante, por lo que puede evitar la mayoría de los problemas de sincronización en el objeto sondeado.
Mentira Ryan
4

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.

Zan Lynx
fuente
2

¡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 Caswellque señaló algo tonto en mi respuesta:

"como usar variables de condición para notificar subprocesos para despertar" Suena como un arreglo basado en eventos / observador, no sondeo

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 PaintSystempodrí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 alertar PaintSystemque tiene trabajo que hacer, pero no especifica nada más que eso, y elPaintSystemestá dedicado a una tarea uniforme y muy homogénea en ese momento. Cuando estamos depurando y rastreando el PaintSystem'scó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:

when there's work to do:
   for each thing:
       apply a very specific and uniform operation to the thing

Opuesto a:

when one specific event happens:
    do something with relevant thing
in relevant thing's event:
    do some more things
in thing1's triggered by thing's event:
    do some more things
in thing2's event triggerd by thing's event:
    do some more things:
in thing3's event triggered by thing2's event:
    do some more things
in thing4's event triggered by thing1's event:
    cause a side effect which shouldn't be happening
    in this order or from this thread.

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:

in thread dedicated to layout and painting:
    when there's work to do:
         for each widget that needs resizing/reposition:
              resize/reposition thing to target size/position
              mark appropriate grid cells as needing repainting
         for each grid cell that needs repainting:
              repaint cell
         go back to sleep

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
1
"como usar variables de condición para notificar subprocesos para despertar" Suena como un arreglo basado en eventos / observador, no sondeo.
Josh Caswell
La diferencia me parece muy sutil, pero la notificación es solo en forma de "Hay trabajo por hacer" para despertar los hilos. Como ejemplo, un patrón de observador podría, al cambiar el tamaño de un control principal, cambiar el tamaño de las llamadas en cascada en la jerarquía utilizando un despacho dinámico. Las cosas tendrían sus funciones de evento de cambio de tamaño llamadas indirectamente de inmediato. Entonces podrían volver a pintarse de inmediato. A continuación, si utilizamos una cola de eventos, cambiar el tamaño de un control de los padres podría empujar los eventos de cambio de tamaño abajo de la jerarquía, momento en el cual las funciones de cambio de tamaño para cada control que podría ser llamado de una manera diferida, en el que ...
... señalan que luego podrían empujar los eventos de repintado que, de la misma manera, se llaman de manera diferida después de que todo haya terminado de cambiar el tamaño, y todo desde un hilo central de manejo de eventos. Y encuentro que la centralización es beneficiosa un poco al menos en cuanto a la depuración y poder razonar fácilmente sobre dónde se está llevando a cabo el procesamiento (incluido qué hilo) ... Entonces, lo que considero que está más cerca del sondeo no es estas soluciones ...
Sería, por ejemplo, tener un LayoutSystemque normalmente está durmiendo, pero cuando el usuario cambia el tamaño de un control, usaría una variable de condición para activar el LayoutSystem. Luego, LayoutSystemcambia 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 se PaintSystemdespierta y atraviesa esas regiones rectangulares, repintando las que necesitan ser redibujadas en un bucle secuencial plano.
Por lo tanto, la variable de condición en sí sigue un patrón de observación para notificar a los subprocesos para que se activen, pero no estamos transmitiendo ninguna información más que "hay trabajo por hacer". Y cada sistema que se activa se dedica a procesar cosas en un bucle muy simple aplicando una tarea muy homogénea, en lugar de una cola de eventos que tiene tareas no homogéneas (podría contener una mezcla ecléctica de eventos para procesar).
-4

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.

Aroop Bhattacharya
fuente
2
esto ni siquiera intenta responder a la pregunta formulada, ¿cuándo sería mejor sondear los eventos que usar un patrón de observación? Vea cómo responder
mosquito