Czy istnieją scenariusze, w których odpytywanie o zdarzenia byłoby lepsze niż stosowanie wzorca obserwatora ? Obawiam się, że użyję odpytywania i zacznę go używać, jeśli ktoś da mi dobry scenariusz. Mogę tylko myśleć o tym, jak wzorzec obserwatora jest lepszy niż odpytywanie. Rozważ ten scenariusz:
Programujesz symulator samochodowy. Samochód jest przedmiotem. Jak tylko samochód się włączy, chcesz odtworzyć klip dźwiękowy „vroom vroom”.
Możesz to wymodelować na dwa sposoby:
Odpytywanie : Odpytuj obiekt samochodu co sekundę, aby sprawdzić, czy jest włączony. Gdy jest włączony, odtwórz klip dźwiękowy.
Wzorzec obserwatora : Ustaw samochód jako obiekt wzorca obserwatora. Niech opublikuje zdarzenie „włączone” dla wszystkich obserwatorów, gdy samo się włączy. Utwórz nowy obiekt dźwiękowy, który nasłuchuje samochodu. Niech implementuje funkcję zwrotną „on”, która odtwarza klip dźwiękowy.
W tym przypadku myślę, że wzór obserwatora wygrywa. Po pierwsze, odpytywanie wymaga więcej procesora. Po drugie, klip dźwiękowy nie uruchamia się natychmiast po włączeniu samochodu. Z powodu okresu odpytywania może być do 1 sekundy przerwy.
źródło
Odpowiedzi:
Wyobraź sobie, że chcesz otrzymywać powiadomienia o każdym cyklu silnika, np. Aby wyświetlić pomiar prędkości obrotowej kierowcy.
Wzorzec obserwatora: silnik publikuje zdarzenie „cyklu silnika” wszystkim obserwatorom dla każdego cyklu. Utwórz detektor, który zlicza zdarzenia i aktualizuje ekran RPM.
Odpytywanie: Wyświetlacz RPM w regularnych odstępach czasu prosi silnik o licznik cykli silnika i odpowiednio aktualizuje wyświetlacz RPM.
W takim przypadku wzorzec obserwatora prawdopodobnie straciłby: cykl silnika jest procesem o wysokiej częstotliwości i wysokim priorytecie, nie chcesz opóźniać ani opóźniać tego procesu tylko w celu aktualizacji wyświetlacza. Nie chcesz także niszczyć puli wątków zdarzeniami cyklu silnika.
PS: Często używam wzorca odpytywania w programowaniu rozproszonym:
Wzorzec obserwatora: Proces A wysyła komunikat do procesu B, który mówi „za każdym razem, gdy wystąpi zdarzenie E, wyślij wiadomość do procesu A”.
Wzorzec sondowania: Proces A regularnie wysyła komunikat do procesu B, który mówi „jeśli zdarzenie E wystąpiło od czasu ostatniego sondowania, wyślij mi teraz wiadomość”.
Wzorzec odpytywania powoduje nieco większe obciążenie sieci. Ale wzorzec obserwatora ma również wady:
źródło
Odpytywanie jest lepsze, jeśli proces odpytywania przebiega znacznie wolniej niż ankiety. Jeśli piszesz zdarzenia do bazy danych, często lepiej jest sondować wszystkich producentów zdarzeń, zbierać wszystkie zdarzenia, które miały miejsce od czasu ostatniego sondowania, a następnie zapisywać je w jednej transakcji. Jeśli spróbujesz zapisać każde zdarzenie w takim stanie, w jakim ono wystąpiło, być może nie będziesz w stanie nadążyć i ostatecznie będziesz mieć problemy po zapełnieniu kolejek wejściowych. Ma to również większy sens w rozproszonych systemach rozproszonych, w których opóźnienie jest wysokie lub konfiguracja i rozłączanie połączenia są drogie. Uważam, że systemy ankietowania są łatwiejsze do napisania i zrozumienia, ale w większości sytuacji obserwatorzy lub konsumenci kierowani zdarzeniami wydają się oferować lepszą wydajność (z mojego doświadczenia).
źródło
Odpytywanie jest o wiele łatwiejsze do pracy w sieci, gdy połączenia mogą się nie powieść, serwery mogą zostać wykonane itp. Pamiętaj, że na koniec dnia gniazdo TCP potrzebuje komunikatów „podtrzymujących”, w przeciwnym razie serwer przejmie klienta odszedł.
Odpytywanie jest również dobre, gdy chcesz aktualizować interfejs użytkownika, ale obiekty leżące u jego podstaw zmieniają się bardzo szybko , nie ma sensu aktualizować interfejsu użytkownika więcej niż kilka razy na sekundę w większości aplikacji.
Pod warunkiem, że serwer może zareagować „bez zmian” przy bardzo niskich kosztach i nie odpytujesz zbyt często i nie masz tysięcy sondujących klientów, wtedy sondowanie działa bardzo dobrze w prawdziwym życiu.
Jednak w przypadkach „ w pamięci ” domyślnie używam wzorca obserwatora, ponieważ zwykle jest to najmniejsza praca.
źródło
Sondowanie ma pewne wady, w zasadzie stwierdziłeś je już w swoim pytaniu.
Jednak może być lepszym rozwiązaniem, jeśli chcesz naprawdę oddzielić obserwowalne od obserwatorów. Ale czasem lepiej byłoby użyć obserwowalnego opakowania dla obserwowanego obiektu w takich przypadkach.
Używałbym odpytywania tylko wtedy, gdy nie można zaobserwować obserwowalnego przy interakcjach z obiektami, co często ma miejsce na przykład przy wyszukiwaniu w bazach danych, gdzie nie ma wywołań zwrotnych. Innym problemem może być wielowątkowość, gdzie często bezpieczniej jest odpytywać i przetwarzać wiadomości niż bezpośrednio wywoływać obiekty, aby uniknąć problemów z współbieżnością.
źródło
Dobry przykład sytuacji, w której odpytywanie przejmuje powiadomienie, spójrz na stosy sieciowe systemu operacyjnego.
To była wielka sprawa dla Linuksa, kiedy stos sieciowy włączył NAPI, interfejs API sieci, który pozwalał sterownikom przejść z trybu przerwania (powiadomienia) do trybu odpytywania.
W przypadku wielu interfejsów Gigabit Ethernet przerywacze często przeciążają procesor, powodując, że system działa wolniej niż powinien. Podczas odpytywania karty sieciowe zbierają pakiety w buforach, aż do odpytywania lub karty nawet zapisują pakiety do pamięci za pośrednictwem DMA. Następnie, gdy system operacyjny jest gotowy, odpytuje kartę pod kątem wszystkich danych i wykonuje standardowe przetwarzanie TCP / IP.
Tryb odpytywania umożliwia procesorowi zbieranie danych Ethernet z maksymalną szybkością przetwarzania bez zbędnego obciążenia przerwania. Tryb przerwania pozwala procesorowi na bezczynność między pakietami, gdy praca nie jest tak zajęta.
Sekret polega na tym, kiedy przejść z jednego trybu do drugiego. Każdy tryb ma zalety i powinien być używany we właściwym miejscu.
źródło
Uwielbiam ankiety! Czy ja Tak! Czy ja Tak! Czy ja Tak! Czy nadal? Tak! Co teraz? Tak!
Jak wspomnieli inni, może być niewiarygodnie nieefektywny, jeśli będziesz sondować tylko po to, aby odzyskać ten sam niezmieniony stan w kółko. Taki jest przepis na spalanie cykli procesora i znaczne skrócenie żywotności baterii na urządzeniach mobilnych. Oczywiście nie jest to marnotrawstwo, jeśli odzyskujesz nowy i znaczący stan za każdym razem w tempie nie szybszym niż to pożądane.
Ale głównym powodem, dla którego uwielbiam ankiety, jest jej prostota i przewidywalność. Możesz prześledzić kod i łatwo zobaczyć, kiedy i gdzie coś się wydarzy oraz w jakim wątku. Jeśli teoretycznie żylibyśmy w świecie, w którym odpytywanie było znikomym marnotrawstwem (chociaż rzeczywistość jest daleka od tego), to uważam, że uprościłoby to utrzymywanie kodu jako ogromnej umowy. I to jest korzyść z odpytywania i ciągnięcia, ponieważ widzę, że moglibyśmy zignorować wydajność, chociaż nie powinniśmy w tym przypadku.
Kiedy zacząłem programować w erze DOS, moje małe gry obracały się wokół odpytywania. Skopiowałem część kodu asemblera z książki, którą ledwo rozumiałem, dotyczącą przerwania klawiatury i zapisałem bufor stanów klawiatury, w którym to momencie moja główna pętla zawsze odpytywała. Czy klawisz strzałki w górę jest wciśnięty? Nie. Czy klawisz strzałki w górę jest wciśnięty? Nie. A teraz? Nie. Teraz? Tak. Ok, rusz gracza.
I chociaż niezwykle marnotrawstwo, stwierdziłem, że o wiele łatwiej jest to uzasadnić w porównaniu do tych dni programowania wielozadaniowego i zdarzeń. Wiedziałem dokładnie, kiedy i gdzie coś się dzieje przez cały czas, i łatwiej było utrzymać stabilną i przewidywalną liczbę klatek bez czkawki.
Od tego czasu zawsze starałem się znaleźć sposób na uzyskanie niektórych korzyści i przewidywalności tego bez faktycznego spalania cykli procesora, takich jak używanie zmiennych warunkowych do powiadamiania wątków o przebudzeniu, w którym momencie mogą pobrać nowy stan, robić swoje i wracać do łóżka, czekając na powiadomienie.
I w jakiś sposób uważam, że kolejki zdarzeń są łatwiejsze do pracy z wzorcami niż obserwatorami, mimo że nadal nie są tak łatwe do przewidzenia, dokąd się wybierzesz i co się stanie. Przynajmniej scentralizują przepływ sterowania obsługą zdarzeń do kilku kluczowych obszarów w systemie i zawsze obsługują te zdarzenia w tym samym wątku zamiast podskakiwać z jednej funkcji do miejsca całkowicie odległego i niespodziewanego poza centralnym wątkiem obsługi zdarzeń. Tak więc dychotomia nie zawsze musi odbywać się między obserwatorami a ankietami. Kolejki zdarzeń są jakby pośrednikiem.
Ale tak, w jakiś sposób uważam, że o wiele łatwiej jest rozumować systemy, które wykonują rzeczy, które są analogicznie bliższe do rodzaju przewidywalnych przepływów kontrolnych, które miałem, kiedy sondowałem wieki temu, jednocześnie przeciwdziałając tendencji do pracy w czasy, kiedy nie nastąpiły żadne zmiany stanu. Jest więc taka korzyść, jeśli możesz to zrobić w sposób, który nie powoduje niepotrzebnego spalania cykli procesora, jak w przypadku zmiennych warunkowych.
Pętle homogeniczne
W porządku, otrzymałem świetny komentarz,
Josh Caswell
który wskazał na pewną głupotę w mojej odpowiedzi:Technicznie sama zmienna warunku stosuje wzorzec obserwatora do budzenia / powiadamiania wątków, więc nazwanie tego „odpytywaniem” byłoby prawdopodobnie niezwykle mylące. Ale uważam, że zapewnia podobną korzyść, jaką znalazłem jako odpytywanie z dni DOS (tylko pod względem przepływu kontroli i przewidywalności). Spróbuję to wyjaśnić lepiej.
W tamtych czasach podobało mi się to, że możesz spojrzeć na sekcję kodu lub prześledzić ją i powiedzieć: „Okej, cała ta sekcja jest poświęcona obsłudze zdarzeń na klawiaturze. W tej sekcji kodu nic więcej się nie wydarzy I wiem dokładnie, co się wydarzy wcześniej i wiem dokładnie, co się wydarzy później (np. Fizyka i rendering). ” Sondowanie stanów klawiatury zapewniło tego rodzaju centralizację przepływu kontroli w zakresie obsługi tego, co powinno nastąpić w odpowiedzi na to zdarzenie zewnętrzne. Nie zareagowaliśmy natychmiast na to zdarzenie zewnętrzne. Odpowiedzieliśmy na to w dogodnym dla nas czasie.
Kiedy używamy systemu opartego na wypychaniu opartego na wzorcu obserwatora, często tracimy te korzyści. Formant może zostać przeskalowany, co wyzwala zdarzenie zmiany rozmiaru. Kiedy go prześledzimy, stwierdzimy, że znajdujemy się w egzotycznej kontroli, która zmienia wiele niestandardowych rzeczy, co powoduje więcej zdarzeń. W efekcie jesteśmy całkowicie zaskoczeni śledzeniem wszystkich tych kaskadowych zdarzeń, co do tego, gdzie skończymy w systemie. Co więcej, możemy odkryć, że wszystko to nawet nie występuje konsekwentnie w danym wątku, ponieważ wątek A może tutaj zmienić rozmiar kontrolki, podczas gdy wątek B również zmienia rozmiar kontrolki później. Dlatego zawsze uważałem to za bardzo trudne do przewidzenia, biorąc pod uwagę, jak trudno jest przewidzieć, gdzie wszystko się wydarzy, a także co się stanie.
Kolejka zdarzeń jest dla mnie trochę łatwiejsza do uzasadnienia, ponieważ upraszcza to, gdzie wszystkie te rzeczy dzieją się przynajmniej na poziomie wątku. Jednak może się zdarzyć wiele różnych rzeczy. Kolejka zdarzeń może zawierać eklektyczną mieszankę zdarzeń do przetworzenia, a każda z nich może nas jeszcze zaskoczyć, jaka kaskada zdarzeń się wydarzyła, kolejność ich przetwarzania i jak odbijamy się po całym miejscu w bazie kodu .
To, co uważam za „najbliższe” odpytywaniu, nie użyłoby kolejki zdarzeń, ale odroczyłoby bardzo jednorodny rodzaj przetwarzania. A
PaintSystem
może zostać zaalarmowany przez zmienną warunkową, że do odmalowania niektórych komórek siatki okna należy wykonać malowanie, w którym to momencie wykonuje prostą sekwencyjną pętlę przez komórki i odświeża wszystko w środku w odpowiedniej kolejności Z. Może istnieć jeden poziom wywołania pośredniej / dynamicznej wysyłki, aby wywołać zdarzenia malowania w każdym widgecie znajdującym się w komórce, którą należy odmalować, ale to wszystko - tylko jedna warstwa wywołań pośrednich. Zmienna warunkowa używa wzorca obserwatora, aby ostrzecPaintSystem
, że ma pracę do wykonania, ale nie określa nic więcej, aPaintSystem
jest w tym momencie poświęcony jednemu jednorodnemu, bardzo jednorodnemu zadaniu. Kiedy debugujemy iPaintSystem's
śledzimy kod, wiemy, że nic innego się nie wydarzy oprócz malowania.Chodzi więc głównie o sprowadzenie systemu do miejsca, w którym te rzeczy wykonują jednorodne pętle w stosunku do danych, nakładając na to bardzo szczególną odpowiedzialność zamiast niejednorodnych pętli w stosunku do różnych typów danych, wykonując wiele obowiązków, jakie możemy uzyskać przy przetwarzaniu kolejki zdarzeń.
Dążymy do tego typu rzeczy:
W przeciwieństwie do:
I tak dalej. I nie musi to być jeden wątek na zadanie. Jeden wątek może zastosować logikę układów (zmiana rozmiaru / repozycjonowanie) do kontrolek GUI i odmalować je, ale może nie obsługiwać kliknięć klawiatury lub myszy. Możesz więc spojrzeć na to jako na poprawę jednorodności kolejki zdarzeń. Ale nie musimy też używać kolejki zdarzeń i przeplatać funkcji zmiany rozmiaru i malowania. Możemy zrobić jak:
Tak więc powyższe podejście wykorzystuje tylko zmienną warunkową, aby powiadomić wątek, gdy jest praca do wykonania, ale nie przeplata różnych rodzajów zdarzeń (zmiana rozmiaru w jednej pętli, malowanie w innej pętli, a nie mieszanka obu) i nie „ t zawracał sobie głowę komunikowaniem, co dokładnie należy wykonać (wątek „odkrywa” to po przebudzeniu, patrząc na ogólnosystemowe stany ECS). Każda wykonywana przez niego pętla ma wtedy bardzo jednorodny charakter, co ułatwia ustalenie kolejności, w której wszystko się dzieje.
Nie jestem pewien, jak nazwać tego rodzaju podejście. Nie widziałem, żeby robiły to inne silniki GUI i jest to moje własne egzotyczne podejście do mojego. Ale zanim spróbowałem zaimplementować wielowątkowe frameworki GUI przy użyciu obserwatorów lub kolejek zdarzeń, miałem ogromne trudności z debugowaniem go, a także natknąłem się na pewne niejasne warunki wyścigowe i impasy, których nie byłem wystarczająco inteligentny, aby naprawić w sposób, który dał mi pewność siebie na temat rozwiązania (niektóre osoby mogą to zrobić, ale nie jestem wystarczająco inteligentny). Mój pierwszy projekt iteracji po prostu nazwał miejsce bezpośrednio przez sygnał, a niektóre miejsca następnie odradzały inne wątki, aby wykonać pracę asynchroniczną, i to było najtrudniejsze do uzasadnienia, a ja potykałem się o warunki wyścigu i impasy. Druga iteracja wykorzystywała kolejkę zdarzeń, co było nieco łatwiejsze do uzasadnienia, ale nie jest to wystarczająco łatwe, aby mój mózg mógł to zrobić, nie wpadając w niejasny impas i warunki wyścigu. Trzecia i ostatnia iteracja wykorzystała podejście opisane powyżej, a na koniec pozwoliło mi to na stworzenie wielowątkowego frameworku GUI, który nawet głupi prostak taki jak ja mógłby poprawnie zaimplementować.
Wtedy ten rodzaj ostatecznego wielowątkowego interfejsu GUI pozwolił mi wymyślić coś innego, co było o wiele łatwiejsze do uzasadnienia i uniknięcie tego rodzaju błędów, które zwykle popełniłem, i jeden z powodów, dla których tak łatwo było mi to uzasadnić w najmniej z powodu tych jednorodnych pętli i tego, jak trochę przypominały one sterowanie, podobnie jak podczas odpytywania w dniach DOS (nawet jeśli tak naprawdę nie odpytuje i wykonuje pracę tylko wtedy, gdy jest do zrobienia). Chodziło o to, aby odejść jak najdalej od modelu obsługi zdarzeń, co pociąga za sobą niejednorodne pętle, niejednorodne skutki uboczne, niejednorodne przepływy kontrolne, i coraz bardziej pracować w kierunku jednorodnych pętli działających jednorodnie na jednorodnych danych i izolowaniu i ujednolicające skutki uboczne w taki sposób, że łatwiej było skoncentrować się na „czym”
źródło
LayoutSystem
normalnie śpiące, ale gdy użytkownik zmieni rozmiar kontrolki, użyje zmiennej warunkowej, aby obudzićLayoutSystem
. NastępnieLayoutSystem
zmienia rozmiar wszystkich niezbędnych elementów sterujących i wraca do snu. W tym procesie prostokątne regiony, w których znajdują się widżety, są oznaczane jako wymagające aktualizacji, w którym to momenciePaintSystem
budzi się i przechodzi przez te prostokątne regiony, odmalowując te, które wymagają przerysowania w płaskiej sekwencyjnej pętli.Przedstawiam wam więcej na temat koncepcyjnego sposobu myślenia o tupotie obserwatora. Pomyśl o scenariuszu, takim jak subskrybowanie kanałów YouTube. Istnieje liczba użytkowników, którzy subskrybują kanał, a gdy na kanale pojawi się jakakolwiek aktualizacja zawierająca wiele filmów, subskrybent otrzyma powiadomienie o zmianie w tym konkretnym kanale. Doszliśmy zatem do wniosku, że jeśli kanał jest SUBJECT, który ma możliwość subskrybowania, anuluj subskrypcję i powiadom wszystkich OBSERWATORÓW zarejestrowanych na kanale.
źródło