Różnica między konsumentem / producentem a obserwatorem / obserwowalna

16

Pracuję nad projektem aplikacji, która składa się z trzech części:

  • pojedynczy wątek, który obserwuje określone zdarzenia (tworzenie plików, żądania zewnętrzne itp.)
  • N wątków roboczych, które reagują na te zdarzenia, przetwarzając je (każdy proces roboczy przetwarza i zużywa pojedyncze zdarzenie, a przetwarzanie może zająć zmienny czas)
  • kontroler, który zarządza tymi wątkami i obsługuje obsługę błędów (restartowanie wątków, rejestrowanie wyników)

Chociaż jest to dość podstawowe i nietrudne do wdrożenia, zastanawiam się, jaki byłby to „właściwy” sposób (w tym konkretnym przypadku w Javie, ale docenia się również odpowiedzi na abstrakcję). Przychodzą mi na myśl dwie strategie:

  • Obserwator / Obserwowalny: Obserwujący wątek jest obserwowany przez kontroler. W przypadku zdarzenia kontroler jest następnie powiadamiany i może przypisać nowe zadanie do wolnego wątku z puli buforowanych wątków wielokrotnego użytku (lub czekać i buforować zadania w kolejce FIFO, jeśli wszystkie wątki są aktualnie zajęte). Wątki robocze implementują możliwość wywoływania i albo zwracają wynik z wynikiem (lub wartością logiczną), albo zwracają z błędem, w którym to przypadku kontroler może zdecydować, co zrobić (w zależności od rodzaju popełnionego błędu).

  • Producent / konsument : Wątek obserwujący współdzieli BlockingQueue z kontrolerem (kolejka zdarzeń), a kontroler dzieli dwa ze wszystkimi pracownikami (kolejka zadań i kolejka wyników). W przypadku zdarzenia wątek obserwujący umieszcza obiekt zadania w kolejce zdarzeń. Kontroler pobiera nowe zadania z kolejki zdarzeń, przegląda je i umieszcza w kolejce zadań. Każdy pracownik czeka na nowe zadania i pobiera je z kolejki zadań (kto pierwszy ten lepszy, zarządzany przez samą kolejkę), odkładając wyniki lub błędy z powrotem do kolejki wyników. Na koniec kontroler może pobrać wyniki z kolejki wyników i podjąć odpowiednie kroki w przypadku błędów.

Wyniki końcowe obu podejść są podobne, ale każde z nich ma niewielkie różnice:

W przypadku obserwatorów kontrola wątków jest bezpośrednia, a każde zadanie przypisywane jest do konkretnego nowego spawnowanego pracownika. Narzut związany z tworzeniem wątków może być wyższy, ale niewiele dzięki buforowanej puli wątków. Z drugiej strony wzorzec Obserwatora jest zredukowany do jednego Obserwatora zamiast wielu, co nie jest dokładnie tym, do czego został zaprojektowany.

Wydaje się, że strategia kolejek jest łatwiejsza do rozszerzenia, na przykład dodanie wielu producentów zamiast jednego jest proste i nie wymaga żadnych zmian. Minusem jest to, że wszystkie wątki działałyby w nieskończoność, nawet gdy w ogóle nie wykonywały żadnej pracy, a obsługa błędów / wyników nie wygląda tak elegancko jak w pierwszym rozwiązaniu.

Jakie byłoby najbardziej odpowiednie podejście w tej sytuacji i dlaczego? Trudno mi znaleźć odpowiedzi na to pytanie w Internecie, ponieważ większość przykładów dotyczy tylko jasnych przypadków, takich jak aktualizacja wielu okien o nową wartość w przypadku Observer lub przetwarzanie z wieloma konsumentami i producentami. Wszelkie uwagi są mile widziane.

użytkownik183536
źródło

Odpowiedzi:

10

Jesteś blisko odpowiedzi na swoje pytanie. :)

We wzorze Observable / Observer (zwróć uwagę na odwrót) należy pamiętać o trzech rzeczach:

  1. Ogólnie powiadomienie o zmianie, tj. „Ładunek”, jest widoczne.
  2. To, co obserwowalne, istnieje .
  3. Obserwatorzy muszą być znane do istniejących obserwowalne (albo nie mają nic do obserwowania on).

Łącząc te punkty, implikuje się to, że obserwowalne wie, jakie są jego dalsze składniki, tj. Obserwatorzy. Przepływ danych jest z natury napędzany przez obserwowalne - obserwatorzy po prostu „żyją i umierają” na podstawie tego, co obserwują.

We wzorcu producent / konsument otrzymujesz zupełnie inną interakcję:

  1. Zasadniczo ładunek istnieje niezależnie od producenta odpowiedzialnego za jego wytworzenie.
  2. Producenci nie wiedzą, jak i kiedy konsumenci są aktywni.
  3. Konsumenci nie muszą znać producenta ładunku.

Przepływ danych jest teraz całkowicie rozdzielony między producentem a konsumentem - wszystko, co producent wie, to to, że ma wynik, a wszystko, co wie, to że ma dane wejściowe. Co ważne, oznacza to, że producenci i konsumenci mogą istnieć całkowicie bez obecności drugiej osoby.

Inną nie tak subtelną różnicą jest to, że wielu obserwatorów na tym samym obserwowalnym zwykle otrzymuje taką samą ładowność (chyba że jest to niekonwencjonalne wdrożenie), podczas gdy wielu konsumentów z tego samego producenta może nie. Zależy to od tego, czy pośrednik jest podobny do kolejki czy tematu. Pierwszy przekazuje inny komunikat dla każdego konsumenta, podczas gdy drugi zapewnia (lub próbuje), że wszyscy konsumenci przetwarzają na podstawie komunikatu.

Aby dopasować je do swojej aplikacji:

  • We wzorcu Obserwowalny / Obserwator, za każdym razem, gdy inicjowany jest obserwowany wątek, musi wiedzieć, jak poinformować kontroler. Jako obserwator kontroler prawdopodobnie czeka na powiadomienie od obserwowanego wątku, zanim pozwoli wątkom obsłużyć zmianę.
  • We wzorcu producent / konsument obserwowany wątek musi tylko znać obecność kolejki zdarzeń i działa wyłącznie z tym. Jako konsument kontroler odpytuje kolejkę zdarzeń, a gdy otrzyma nowy ładunek, pozwala wątkom sobie z tym poradzić.

Dlatego, aby bezpośrednio odpowiedzieć na twoje pytanie: jeśli chcesz zachować pewien poziom separacji między oglądanym wątkiem a kontrolerem, tak abyś mógł obsługiwać je niezależnie, powinieneś dążyć do wzorca Producent / Konsument.

hjk
źródło
2
Dziękuję za szczegółową odpowiedź. Niestety nie mogę głosować za tym z powodu utraty reputacji, więc zamiast tego oznaczyłem go jako rozwiązanie. Czasowa niezależność między obiema częściami, o których wspomniałeś, jest czymś pozytywnym, o czym dotąd nie myślałem. Kolejki mogą zarządzać krótkimi seriami wielu zdarzeń z długimi przerwami między znacznie lepszymi niż bezpośrednie działanie po zaobserwowaniu zdarzeń (jeśli maksymalna liczba wątków jest stała i względnie niska). Liczbę wątków można również dynamicznie zwiększać / zmniejszać w zależności od bieżącej liczby elementów kolejki.
user183536,
@ user183536 Żadnych problemów, chętnie pomogę! :)
hjk