Diferença entre Consumidor / Produtor e Observador / Observável

15

Estou trabalhando no design de um aplicativo que consiste em três partes:

  • um único encadeamento que observa determinados eventos (criação de arquivos, solicitações externas, etc.)
  • N encadeamentos do trabalhador que respondem a esses eventos processando-os (cada trabalhador processa e consome um único evento, e o processamento pode levar um tempo variável)
  • um controlador que gerencia esses encadeamentos e manipula erros (reiniciando encadeamentos, registrando resultados)
Embora isto seja bastante básico e não seja difícil de implementar, eu estou querendo saber qual seria a maneira "certa" de fazê-lo (neste caso concreto em Java, mas as respostas de abstração mais altas também são apreciadas). Duas estratégias vêm à mente:

  • Observador / Observável: O encadeamento de observação é observado pelo controlador. No caso de um evento acontecer, o controlador será notificado e poderá atribuir a nova tarefa a um encadeamento livre a partir de um conjunto de encadeamentos em cache reutilizável (ou aguardar e armazenar em cache as tarefas na fila FIFO se todos os encadeamentos estiverem ocupados atualmente). Os threads de trabalho implementam Callable e retornam com êxito com o resultado (ou um valor booleano), ou retornam com um erro, caso em que o controlador pode decidir o que fazer (dependendo da natureza do erro que ocorreu).

  • Produtor / Consumidor : O encadeamento de monitoramento compartilha um BlockingQueue com o controlador (fila de eventos) e o controlador compartilha dois com todos os trabalhadores (fila de tarefas e fila de resultados). No caso de um evento, o thread de exibição coloca um objeto de tarefa na fila de eventos. O controlador pega novas tarefas da fila de eventos, revisa-as e coloca-as na fila de tarefas. Cada trabalhador aguarda novas tarefas e as recebe / consome da fila de tarefas (primeiro a chegar, primeiro administrado pela própria fila), colocando os resultados ou erros de volta na fila de resultados. Por fim, o controlador pode recuperar os resultados da fila de resultados e executar etapas de acordo com os erros.

Os resultados finais de ambas as abordagens são semelhantes, mas cada um deles tem pequenas diferenças:

Com Observers, o controle de threads é direto e cada tarefa é atribuída a um novo trabalhador específico. A sobrecarga para criação de encadeamentos pode ser maior, mas não muito, graças ao pool de encadeamentos em cache. Por outro lado, o padrão Observer é reduzido a um único Observer em vez de múltiplos, o que não é exatamente o que foi projetado.

A estratégia de fila parece ser mais fácil de estender, por exemplo, adicionar vários produtores em vez de um é simples e não requer nenhuma alteração. A desvantagem é que todos os encadeamentos seriam executados indefinidamente, mesmo quando não estão fazendo nenhum trabalho, e o tratamento de erros / resultados não parece tão elegante quanto na primeira solução.

Qual seria a abordagem mais adequada nessa situação e por quê? Achei difícil encontrar respostas para essa questão on-line, porque a maioria dos exemplos trata apenas de casos claros, como a atualização de muitas janelas com um novo valor no caso do Observer ou o processamento com vários consumidores e produtores. Qualquer entrada é muito apreciada.

    
por user183536 14.06.2015 / 17:00
fonte

1 resposta

10

Você está bem perto de responder sua própria pergunta. :)

No padrão Observable / Observer (observe o flip), há três coisas a serem lembradas:

  1. Geralmente, a notificação da alteração, ou seja, "carga útil", está no campo de observação.
  2. O observável existe .
  3. Os observadores devem ser conhecidos pelo existente observável (ou então eles não têm nada para observar).

Combinando esses pontos, o que está implícito é que o observável sabe quais são seus componentes a jusante, ou seja, os observadores. O fluxo de dados é inerentemente impulsionado pelos observáveis - os observadores meramente "vivem e morrem" pelo que estão observando.

No padrão Produtor / Consumidor, você obtém uma interação muito diferente:

  1. Geralmente, a carga útil existe independentemente do produtor responsável pela produção.
  2. Os produtores não sabem como ou quando os consumidores estão ativos.
  3. Os consumidores não precisam saber o produtor da carga útil.

O fluxo de dados é agora completamente cortado entre um produtor e um consumidor - tudo o que o produtor sabe é que ele tem uma saída, e tudo que o consumidor sabe é que ele tem uma entrada. Importante, isso significa que produtores e consumidores podem existir inteiramente sem a presença do outro.

Outra diferença não tão sutil é que vários observadores no mesmo observável geralmente obtêm a mesma carga (a menos que haja uma implementação não convencional), enquanto vários consumidores fora do mesmo produtor não pode. Isso depende se o intermediário for uma abordagem semelhante a uma fila ou semelhante a um tópico. O primeiro passa uma mensagem diferente para cada consumidor, enquanto o segundo garante (ou tenta) que todos os consumidores processem por mensagem.

Para ajustá-los ao seu aplicativo:

  • No padrão Observable / Observer, sempre que seu thread de exibição estiver sendo inicializado, ele deve saber como informar o controlador. Como observador, o controlador provavelmente está aguardando uma notificação do encadeamento de monitoramento antes de permitir que os encadeamentos manipulem a alteração.
  • No padrão Produtor / Consumidor, seu tópico de exibição precisa saber apenas a presença da fila de eventos e interagir apenas com isso. Como o consumidor, o controlador consulta a fila de eventos e, assim que recebe uma nova carga útil, permite que os segmentos lidem com ela.
Portanto, para responder à sua pergunta diretamente: se você deseja manter um certo nível de separação entre seu encadeamento de observação e seu controlador para poder operá-los independentemente, você deve tender para o padrão Produtor / Consumidor.

    
por 16.06.2015 / 11:43
fonte