Разница между потребителем / производителем и наблюдателем / наблюдаемой

15

Я работаю над дизайном приложения, которое состоит из трех частей:

  • один поток, который отслеживает определенные события (создание файлов, внешние запросы и т. д.)
  • N рабочих потоков, которые отвечают на эти события, обрабатывая их (каждый рабочий обрабатывает и использует одно событие, и обработка может занять переменное время)
  • контроллер, который управляет этими потоками и обрабатывает ошибки (перезапуск потоков, регистрация результатов)

Хотя это довольно просто и не сложно реализовать, мне интересно, что было бы «правильным» способом сделать это (в данном конкретном случае в Java, но также приветствуются ответы с более высокой абстракцией). На ум приходят две стратегии:

  • Наблюдатель / Наблюдаемый: наблюдающий поток контролируется контроллером. В случае возникновения события контроллер уведомляется и может назначить новую задачу свободному потоку из пула многократно кэшированных потоков (или подождать и кэшировать задачи в очереди FIFO, если все потоки в данный момент заняты). Рабочие потоки реализуют Callable и либо возвращают успешно результат (или логическое значение), либо возвращают с ошибкой, и в этом случае контроллер может решить, что делать (в зависимости от характера возникшей ошибки).

  • Производитель / Потребитель : Наблюдающий поток совместно использует BlockingQueue с контроллером (очередь событий), а контроллер делит два со всеми работниками (очередь задач и очередь результатов). В случае события наблюдающий поток помещает объект задачи в очередь событий. Контроллер берет новые задачи из очереди событий, просматривает их и помещает в очередь задач. Каждый работник ждет новых задач и получает / использует их из очереди задач (первым пришел, первым обслужен, управляется самой очередью), помещая результаты или ошибки обратно в очередь результатов. Наконец, контроллер может получить результаты из очереди результатов и предпринять соответствующие шаги в случае ошибок.

Конечные результаты обоих подходов похожи, но каждый из них имеет небольшие различия:

В Observers управление потоками осуществляется напрямую, и каждая задача присваивается определенному новому порожденному работнику. Накладные расходы на создание потоков могут быть выше, но не очень благодаря кэшированному пулу потоков. С другой стороны, шаблон Observer сводится к одному Observer вместо нескольких, что не совсем то, для чего он был разработан.

Стратегию очереди, кажется, легче расширить, например, добавление нескольких производителей вместо одного является простым и не требует каких-либо изменений. Недостатком является то, что все потоки будут работать бесконечно, даже если они вообще не будут работать, а обработка ошибок / результатов не будет выглядеть так элегантно, как в первом решении.

Какой подход был бы наиболее подходящим в этой ситуации и почему? Мне было трудно найти ответы на этот вопрос в Интернете, потому что большинство примеров имеют дело только с понятными случаями, такими как обновление многих окон новым значением в случае Observer или обработка с несколькими потребителями и производителями. Любой вклад с благодарностью.

user183536
источник

Ответы:

10

Вы почти готовы ответить на свой вопрос. :)

В паттерне Observable / Observer (обратите внимание на переворот) необходимо помнить три вещи:

  1. Как правило, уведомление об изменении, то есть «полезная нагрузка», находится в наблюдаемом.
  2. Наблюдаемое существует .
  3. Наблюдатели должны быть известны существующим наблюдаемым (иначе им нечего наблюдать).

Сочетая эти моменты, подразумевается, что наблюдаемая знает, каковы ее нижестоящие компоненты, то есть наблюдатели. Поток данных по своей природе обусловлен наблюдаемыми - наблюдатели просто «живут и умирают» тем, на чем они наблюдают.

В шаблоне производитель / потребитель вы получаете совершенно другое взаимодействие:

  1. Как правило, полезная нагрузка существует независимо от производителя, ответственного за ее создание.
  2. Производители не знают, как или когда потребители активны.
  3. Потребителям не нужно знать производителя полезных данных.

Поток данных теперь полностью разделен между производителем и потребителем - все, что производитель знает, это то, что у него есть выход, и все, что потребитель знает, это то, что у него есть вход. Важно отметить, что это означает, что производители и потребители могут существовать полностью без присутствия других.

Другое не столь тонкое отличие состоит в том, что несколько наблюдателей в одной и той же наблюдаемой обычно получают одну и ту же полезную нагрузку (если нет нетрадиционной реализации), тогда как несколько потребителей от одного и того же производителя не могут. Это зависит от того, является ли посредник подходом, подобным очереди, или темой. Первый передает разные сообщения для каждого потребителя, а второй гарантирует (или пытается), что все потребители обрабатывают для каждого сообщения.

Чтобы вписать их в ваше приложение:

  • В шаблоне Observable / Observer каждый раз, когда ваш поток наблюдения инициализируется, он должен знать, как сообщить контроллеру. Как наблюдатель, контроллер, вероятно, ожидает уведомления от потока наблюдения, прежде чем он позволит потокам обрабатывать изменения.
  • В шаблоне Producer / Consumer ваш поток наблюдения должен знать только наличие очереди событий и взаимодействовать исключительно с этим. Как потребитель, контроллер затем опрашивает очередь событий и, как только он получает новую полезную нагрузку, он позволяет потокам обрабатывать ее.

Поэтому, чтобы ответить на ваш вопрос напрямую: если вы хотите поддерживать некоторый уровень разделения между вашим потоком наблюдения и вашим контроллером, чтобы вы могли управлять ими независимо, вы должны стремиться к шаблону «производитель / потребитель».

ХИК
источник
2
Спасибо за ваш подробный ответ. К сожалению, я не могу оценить это из-за отсутствия репутации, поэтому вместо этого я отметил это как решение. Временная независимость между обеими частями, о которой вы упомянули, является чем-то позитивным, о чем я не думал до сих пор. Очереди могут управлять короткими пакетами многих событий с длинными паузами между ними намного лучше, чем прямое действие после наблюдения событий (если максимальное число потоков фиксировано и относительно мало). Количество потоков также может динамически увеличиваться / уменьшаться в зависимости от текущего количества элементов очереди.
user183536
@ user183536 Без проблем, рад помочь! :)
hjk