¡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é"