Diferencia entre consumidor / productor y observador / observable

15

Estoy trabajando en el diseño de una aplicación que consta de tres partes:

  • un solo hilo que vigila la ocurrencia de ciertos eventos (creación de archivos, solicitudes externas, etc.)
  • N subprocesos de trabajo que responden a estos eventos al procesarlos (cada trabajador procesa y consume un solo evento y el procesamiento puede tomar un tiempo variable)
  • un controlador que gestiona esos subprocesos y maneja los errores (reinicio de subprocesos, registro de resultados)

Aunque esto es bastante básico y no es difícil de implementar, me pregunto cuál sería la forma "correcta" de hacerlo (en este caso concreto en Java, pero también se agradecen las respuestas de abstracción más altas). Se me ocurren dos estrategias:

  • Observador / Observable: El controlador observa el hilo de observación. En caso de que ocurra un evento, se notifica al controlador y puede asignar la nueva tarea a un subproceso libre de un grupo de subprocesos en caché reutilizable (o esperar y almacenar en caché las tareas en la cola FIFO si todos los subprocesos están ocupados actualmente). Los subprocesos de trabajo implementan Callable y devuelven correctamente con el resultado (o un valor booleano), o regresan con un error, en cuyo caso el controlador puede decidir qué hacer (dependiendo de la naturaleza del error que ha sucedido).

  • Productor / Consumidor : el hilo de observación comparte un BlockingQueue con el controlador (cola de eventos) y el controlador comparte dos con todos los trabajadores (cola de tareas y cola de resultados). En caso de un evento, el hilo de observación coloca un objeto de tarea en la cola de eventos. El controlador toma nuevas tareas de la cola de eventos, las revisa y las coloca en la cola de tareas. Cada trabajador espera nuevas tareas y las toma / consume de la cola de tareas (por orden de llegada, gestionadas por la misma cola), colocando los resultados o errores nuevamente en la cola de resultados. Finalmente, el controlador puede recuperar los resultados de la cola de resultados y tomar los pasos correspondientes en caso de errores.

Los resultados finales de ambos enfoques son similares, pero cada uno tiene ligeras diferencias:

Con Observers, el control de los hilos es directo y cada tarea se atribuye a un nuevo trabajador generado específico. La sobrecarga para la creación de subprocesos puede ser mayor, pero no mucho gracias al grupo de subprocesos en caché. Por otro lado, el patrón de Observador se reduce a un solo Observador en lugar de múltiples, que no es exactamente para lo que fue diseñado.

La estrategia de la cola parece ser más fácil de extender, por ejemplo, agregar múltiples productores en lugar de uno es sencillo y no requiere ningún cambio. La desventaja es que todos los subprocesos se ejecutarían indefinidamente, incluso cuando no se realiza ningún trabajo, y el manejo de errores / resultados no se ve tan elegante como en la primera solución.

¿Cuál sería el enfoque más adecuado en esta situación y por qué? Me resulta difícil encontrar respuestas a esta pregunta en línea, porque la mayoría de los ejemplos solo tratan casos claros, como actualizar muchas ventanas con un nuevo valor en el caso del Observador o procesar con múltiples consumidores y productores. Cualquier aporte es muy apreciado.

usuario183536
fuente

Respuestas:

10

Estás bastante cerca de responder tu propia pregunta. :)

En el patrón Observable / Observador (tenga en cuenta el cambio), hay tres cosas a tener en cuenta:

  1. En general, la notificación del cambio, es decir, "carga útil", se puede observar.
  2. Lo observable existe .
  3. Los observadores deben ser conocidos por el observable existente (de lo contrario, no tienen nada para observar)

Al combinar estos puntos, lo que está implícito es que el observable sabe cuáles son sus componentes aguas abajo, es decir, los observadores. El flujo de datos es inherentemente impulsado por lo observable: los observadores simplemente "viven y mueren" por lo que están observando.

En el patrón Productor / Consumidor, obtienes una interacción muy diferente:

  1. En general, la carga útil existe independientemente del productor responsable de producirla.
  2. Los productores no saben cómo o cuándo están activos los consumidores.
  3. Los consumidores no necesitan conocer al productor de la carga útil.

El flujo de datos ahora está completamente separado entre un productor y un consumidor: todo lo que el productor sabe es que tiene una salida, y todo lo que sabe el consumidor es que tiene una entrada. Es importante destacar que esto significa que los productores y consumidores pueden existir completamente sin la presencia del otro.

Otra diferencia no tan sutil es que múltiples observadores en el mismo observable generalmente obtienen la misma carga útil (a menos que haya una implementación no convencional), mientras que múltiples consumidores fuera del mismo productor pueden no tenerlo. Esto depende si el intermediario es un enfoque tipo cola o tema. El primero pasa un mensaje diferente para cada consumidor, mientras que el segundo asegura (o intenta) que todos los consumidores procesen por mensaje.

Para adaptarlos a su aplicación:

  • En el patrón Observable / Observador, cada vez que su hilo de observación se está inicializando, debe saber cómo informar al controlador. Como observador, es probable que el controlador esté esperando una notificación del hilo de observación antes de permitir que los hilos manejen el cambio.
  • En el patrón Productor / Consumidor, su hilo de observación solo necesita saber la presencia de la cola del evento e interactúa únicamente con eso. Como consumidor, el controlador sondea la cola de eventos y, una vez que obtiene una nueva carga útil, permite que los subprocesos la manejen.

Por lo tanto, para responder su pregunta directamente: si desea mantener un cierto nivel de separación entre su hilo de observación y su controlador de modo que pueda operarlos de forma independiente, debe tender hacia el patrón Productor / Consumidor.

hjk
fuente
2
Gracias por tu respuesta detallada. Desafortunadamente, no puedo votarlo por falta de reputación, así que lo marqué como una solución. La independencia temporal entre ambas partes que mencionaste es algo positivo que no había pensado hasta ahora. Las colas podrían gestionar ráfagas cortas de muchos eventos con pausas largas entre mucho mejores que la acción directa después de que se hayan observado los eventos (si el conteo máximo de hilos es fijo y relativamente bajo). El recuento de subprocesos también se puede aumentar / disminuir dinámicamente según los recuentos de elementos de la cola actual.
user183536
@ user183536 No hay problemas, me alegro de ayudar! :)
hjk