Czy pętla zdarzeń jest tylko pętlą for / while ze zoptymalizowanym odpytywaniem?

55

Próbuję zrozumieć, czym jest pętla zdarzeń. Często wyjaśnia się, że w pętli zdarzeń robisz coś, dopóki nie zostaniesz powiadomiony o wystąpieniu zdarzenia. Następnie zajmujesz się zdarzeniem i kontynuujesz robienie tego, co robiłeś wcześniej.

Aby zmapować powyższą definicję na przykładzie. Mam serwer, który „nasłuchuje” w pętli zdarzeń, a gdy zostanie wykryte połączenie przez gniazdo, dane z niego zostaną odczytane i wyświetlone, po czym serwer wznowi / zacznie nasłuchiwać tak jak wcześniej.


Jednak to wydarzenie się dzieje i otrzymujemy powiadomienie „tak po prostu”, to dla mnie zbyt wiele. Możesz powiedzieć: „To nie jest tak po prostu”, musisz zarejestrować detektora zdarzeń ”. Ale czym jest detektor zdarzeń, ale funkcja, która z jakiegoś powodu nie powraca. Czy jest w swojej własnej pętli i czeka na powiadomienie o zdarzeniu? Czy detektor zdarzeń powinien również zarejestrować detektor zdarzeń? Gdzie to się kończy?


Wydarzenia to fajna abstrakcja do pracy, jednak tylko abstrakcja. Uważam, że w końcu odpytywanie jest nieuniknione. Być może nie robimy tego w naszym kodzie, ale robią to za nas niższe poziomy (implementacja języka programowania lub system operacyjny).

Zasadniczo sprowadza się do następującego pseudo kodu, który działa gdzieś wystarczająco nisko, więc nie powoduje zajętego oczekiwania:

while(True):
    do stuff
    check if event has happened (poll)
    do other stuff

Takie jest moje rozumienie całego pomysłu i chciałbym usłyszeć, czy jest to poprawne. Otwarcie zgadzam się z tym, że cały pomysł jest zasadniczo błędny, w takim przypadku chciałbym uzyskać prawidłowe wyjaśnienie.

TheMeaningfulEngineer
źródło
3
Systemy zdarzeń są implementacjami wzorca Observer . Zrozumienie wzorca powinno ugruntować twoje zrozumienie wydarzeń. Sondowanie nie jest wymagane.
Steven Evers
1
Ostatecznie tak, nawet jeśli język konstruuje go abstrakcyjnie.
GrandmasterB
@ SteveEvers W linku wiki. Co EventSourcerobi, jeśli nie odpytuje wejścia klawiatury?
TheMeaningfulEngineer
@Alan: To może robić wszystko, ale konkretnie do wprowadzania z klawiatury istnieją interfejsy API, które rejestrują twoją aplikację jako nasłuchującą zdarzeń na klawiaturze, których możesz użyć jako źródła zdarzeń bez udziału sondowania. Oczywiście, jeśli zejdziesz wystarczająco daleko, USB zawsze odpytuje, ale załóżmy, że używamy klawiatury PS / 2, która jest sterowana przerwaniami, a następnie masz stos wejściowy klawiatury oparty na zdarzeniach z zerowym odpytywaniem.
Phoshi
Mam takie pytania od lat, ale nie mogę powiedzieć, dlaczego nigdy nie zadałem sobie trudu, aby je zadać. Dzięki, jestem teraz zadowolony ze zrozumienia, które oświadczył mnie @Karl Bielefeldt.
0xc0de,

Odpowiedzi:

54

Większość pętli zdarzeń zostanie zawieszonych, jeśli nie będą gotowe żadne zdarzenia, co oznacza, że ​​system operacyjny nie da zadania zadaniu do czasu wystąpienia zdarzenia.

Powiedz, że zdarzenie jest wciśnięte. Możesz zapytać, czy gdzieś w systemie operacyjnym jest pętla sprawdzająca naciśnięcia klawiszy. Odpowiedź brzmi nie. Naciśnięcie klawiszy generuje przerwanie , które jest obsługiwane asynchronicznie przez sprzęt. Podobnie w przypadku timerów, ruchów myszy, nadchodzącego pakietu itp.

W rzeczywistości dla większości systemów operacyjnych odpytywanie o zdarzenia jest abstrakcją. Sprzęt i system operacyjny obsługują zdarzenia asynchronicznie i umieszczają je w kolejce, którą mogą odpytywać aplikacje. Prawdziwe odpytywanie jest widoczne tylko na poziomie sprzętowym w systemach wbudowanych, a nawet tam nie zawsze.

Karl Bielefeldt
źródło
4
Czy przerwa nie jest zmianą napięcia na przewodzie? Czy może to wyzwalać zdarzenie samo w sobie, czy też musimy sondować pin pod kątem wartości napięcia?
TheMeaningfulEngineer
8
To samo może wywołać zdarzenie. Procesor jest tak zaprojektowany. W rzeczywistości procesor może zostać obudzony ze snu przez przerwanie.
Karl Bielefeldt
2
Jestem mylony ze stwierdzeniem Most event loops will block. Jak to pasuje do „paradygmatu pętli zdarzeń, w przeciwieństwie do używania wątków, wykorzystuje nieblokujące wywołania asynchroniczne”?
TheMeaningfulEngineer
1
Jeśli spojrzysz na pętle zdarzeń dla biblioteki takiej jak GTK +, sprawdzają one nowe zdarzenia, a następnie wywołują procedury obsługi zdarzeń w pętli, ale jeśli nie ma żadnych zdarzeń, blokują semafor, timer lub coś w tym rodzaju. Poszczególni programiści tworzą własne pętle zdarzeń, które nie blokują pustej kolejki zdarzeń, ale wszystkie powszechnie używane biblioteki blokują. W przeciwnym razie pętle zdarzeń są zbyt nieefektywne.
Karl Bielefeldt
1
Podejrzewam, że można na to spojrzeć w ten sposób, ale jest to wielowątkowość w bibliotece i / lub systemie operacyjnym zamiast w kodzie aplikacji. Pętle zdarzeń usuwają problemy z synchronizacją dla programisty aplikacji.
Karl Bielefeldt
13

Myślę o odbiorniku zdarzeń nie jako o funkcji działającej we własnej pętli, ale jako o sztafecie z pierwszym biegaczem czekającym na pistolet startowy. Ważnym powodem używania zdarzeń zamiast odpytywania jest to, że są one bardziej wydajne przy cyklach procesora. Dlaczego? Spójrz na to od strony sprzętowej (zamiast kodu źródłowego).

Rozważ serwer WWW. Kiedy serwer dzwoni listen()i blokuje, kod zajmuje jego miejsce jako przekaźnik. Gdy nadejdzie pierwszy pakiet nowego połączenia, karta sieciowa rozpoczyna wyścig, przerywając system operacyjny. System operacyjny uruchamia procedurę obsługi przerwań (ISR), która pobiera pakiet. ISR przekazuje pałeczkę do procedury wyższego poziomu, która ustanawia połączenie. Po nawiązaniu połączenia procedura przekazuje pałeczkę do listen(), co przekazuje pałeczkę do kodu. W tym momencie możesz robić, co chcesz, korzystając z połączenia. Z tego co wiemy, między wyścigami każdy biegacz sztafetowy może iść do pubu. Siłą abstrakcji zdarzeń jest to, że Twój kod nie musi wiedzieć ani się tym przejmować.

Niektóre systemy operacyjne zawierają kod obsługi zdarzeń, który uruchamia swoją część wyścigu, odkłada pałeczkę, a następnie zapętla się z powrotem do punktu początkowego, aby czekać na rozpoczęcie następnego wyścigu. W tym sensie obsługa zdarzeń jest zoptymalizowana odpytywaniem w wielu równoległych pętlach. Jednak zawsze istnieje zewnętrzny wyzwalacz, który rozpoczyna proces. Detektor zdarzeń nie jest funkcją, która nie zwraca wartości, ale funkcją, która czeka na ten zewnętrzny wyzwalacz przed jego uruchomieniem. Zamiast:

while(True):
    do stuff
    check if event has happened (poll)
    do other stuff

Myślę o tym jako:

on(some event):    //I got the baton
     do stuff
     signal the next level up    //Pass the baton

a między signalnastępnym a następnym uruchomieniem modułu obsługi nie ma koncepcyjnego uruchamiania ani zapętlania kodu.

cxw
źródło
Ma to jednak sens, co robi pętla zdarzeń, gdy czeka ona na przerwanie? Jeśli blokuje, czy nie jest to podejście „wielowątkowe”, które jest paradygmatem przeciwstawnym do pętli zdarzeń?
TheMeaningfulEngineer
Jeśli Twój kod ma pętlę forever: { select(); do stuff; }, to za każdym razem ponownie bierzesz udział w wyścigu. Niezależnie od tego, czy robisz to wielokrotnie z jednego wątku, czy równolegle na osobnych wątkach lub procesorach, każde wydarzenie traktuję jak własną rasę. Na przykład przeglądarka internetowa to program wielowątkowy z wieloma pętlami zdarzeń w osobnych wątkach, co najmniej jeden dla interfejsu użytkownika i jeden dla każdej pobieranej strony. Pytanie, które zadaję przy kodowaniu, brzmi: „jak mogę przetwarzać zdarzenia wystarczająco szybko?” Czasami odpowiedzią jest pętla, czasem wątki, często kombinacja.
cxw
Zajmuję się programowaniem opartym na zdarzeniach od kilku dekad i uważam, że ta odpowiedź jest bardzo myląca. Aby detektor działał, musi istnieć pętla, która czeka na zdarzenie, a następnie kieruje je do wszystkich zarejestrowanych słuchaczy. (Zakładając, że mówimy raczej o oprogramowaniu niż o przerwach sprzętowych)
Bryan Oakley
8

Nie. To nie jest „zoptymalizowane odpytywanie”. Pętla zdarzeń wykorzystuje we / wy sterowane przerwaniami zamiast odpytywania.

Pętle While, Until, For itp. Są pętlami odpytywania.

„Odpytywanie” to proces wielokrotnego sprawdzania czegoś. Ponieważ kod pętli jest wykonywany w sposób ciągły, a ponieważ jest małą, „ciasną” pętlą, procesor ma mało czasu na przełączanie zadań i wykonywanie innych czynności. Niemal wszystkie „zawieszanie się”, „zawieszanie się”, „zawieszanie się” lub jakkolwiek chcesz to nazwać, gdy komputer przestaje odpowiadać, są przejawem zablokowania kodu w niezamierzonej pętli odpytywania. Oprzyrządowanie pokaże 100% użycia procesora.

Pętle zdarzeń sterowane przerwaniami są znacznie bardziej wydajne niż pętle odpytywania. Odpytywanie to wyjątkowo marnotrawstwo wykorzystania cykli procesora, dlatego dokładamy wszelkich starań, aby go wyeliminować lub zminimalizować.

Jednak, aby zoptymalizować jakość kodu, większość języków próbuje używać paradygmatu pętli odpytywania tak blisko, jak to możliwe, do poleceń obsługi zdarzeń, ponieważ służą one funkcjonalnie do podobnych celów w programie. Tak więc, ponieważ odpytywanie jest bardziej znanym sposobem oczekiwania na naciśnięcie klawisza lub coś takiego, niedoświadczonym łatwo jest go użyć i skończyć z programem, który sam może działać dobrze, ale nic innego nie działa podczas jego działania. „Przejął” maszynę.

Jak wyjaśniono w innych odpowiedziach, w przekazywaniu zdarzeń sterowanych przerwaniami zasadniczo „CPU” jest ustawiany w CPU, a proces jest „zawieszany” (nie wolno go uruchamiać), dopóki ta flaga nie zostanie zmieniona przez inny proces (taki jak klawiatura) sterownik zmienia go, gdy użytkownik naciśnie klawisz). Jeśli flaga jest faktycznym stanem sprzętowym, takim jak „podniesienie linii”, nazywa się to „przerwaniem” lub „przerwaniem sprzętowym”. Większość jednak jest zaimplementowana jako adres pamięci procesora lub pamięci głównej (RAM) i nazywa się je „semaforami”.

Semafory można zmieniać pod kontrolą oprogramowania, dzięki czemu mogą zapewnić bardzo szybki i prosty mechanizm sygnalizacji między procesami oprogramowania.

Przerwania można jednak zmienić tylko sprzętowo. Najbardziej powszechnym zastosowaniem przerwań jest wyzwalanie w regularnych odstępach czasu przez wewnętrzny układ zegarowy. Jednym z niezliczonych rodzajów akcji oprogramowania aktywowanych przez przerwania zegara jest zmiana semaforów.

Dużo pomijałem, ale musiałem gdzieś się zatrzymać. Zapytaj, czy potrzebujesz więcej szczegółów.

DocSalvager
źródło
7

Zazwyczaj odpowiedzią jest sprzęt, system operacyjny i wątki w tle, nad którymi nie kontrolujesz konspiracji, aby wyglądało to na łatwe. Karta sieciowa odbiera niektóre dane, które wywołuje przerwanie, aby poinformować CPU. Obsługuje go program obsługi przerwań systemu operacyjnego. Następnie wątek w tle, którego nie kontrolujesz (który został utworzony przez zarejestrowanie się w wydarzeniu i śpi od momentu zarejestrowania się w nim) jest budzony przez system operacyjny w ramach obsługi zdarzenia i uruchamia procedurę obsługi zdarzenia.

kamieniste
źródło
7

Sprzeciwiam się wszystkim pozostałym odpowiedziom, które do tej pory widzę, i mówię „tak” . Myślę, że inne odpowiedzi zbyt komplikują sprawy. Z koncepcyjnego punktu widzenia wszystkie pętle zdarzeń są zasadniczo:

while <the_program_is_running> {
    event=wait_for_next_event()
    process_event(event)
}

Jeśli próbujesz zrozumieć pętle zdarzeń po raz pierwszy, myślenie o nich jako o prostej pętli nie zaszkodzi. Niektóre podstawowe struktury czekają, aż system operacyjny dostarczy zdarzenie, a następnie kieruje je do jednego lub kilku modułów obsługi, a następnie czeka na następne zdarzenie i tak dalej. To naprawdę wszystko z punktu widzenia aplikacji.

Bryan Oakley
źródło
1
+1 za prostotę. Ale nacisk kładziony jest na „czekanie”; wykonanie tego kodu zatrzyma się w pierwszym wierszu pętli i nie będzie kontynuowane do momentu wystąpienia zdarzenia. Różni się to od zajętej pętli odpytywania, która jest wielokrotnie sprawdzana pod kątem zdarzenia, dopóki ono nie wystąpi.
Tom Panning
1

Nie wszystkie wyzwalacze zdarzeń są obsługiwane w pętlach. Sposób, w jaki często piszę własne silniki zdarzeń, wyglądałby następująco:

interface Listener {
    void handle (EventInfo info);
}

List<Listener> registeredListeners

void triggerEvent (EventInfo info) {
    foreach (listener in registeredListeners) { // Memo 1
        listener.handle(info) // the handling may or may not be synchronous... your choice
    }
}

void somethingThatTriggersAnEvent () {
    blah
    blah
    blah
    triggerEvent(someGeneratedEventInfo)
    more blah
}

Zauważ, że chociaż Memo 1 jest w pętli, pętla służy do powiadamiania każdego słuchacza. Sam wyzwalacz zdarzenia niekoniecznie jest zapętlony.

Teoretycznie kluczowe zdarzenia na poziomie systemu operacyjnego mogą korzystać z tej samej techniki (choć myślę, że zamiast tego często odpytują? Spekuluję tutaj), pod warunkiem, że system operacyjny udostępnia jakiś registerListenerinterfejs API.

Thomas Eding
źródło