Zrozumienie pętli zdarzeń

136

Myślę o tym i oto co wymyśliłem:

Zobaczmy ten kod poniżej:

console.clear();
console.log("a");
setTimeout(function(){console.log("b");},1000);
console.log("c");
setTimeout(function(){console.log("d");},0);

Przychodzi żądanie i silnik JS rozpoczyna wykonywanie powyższego kodu krok po kroku. Pierwsze dwa połączenia to połączenia synchronizacyjne. Ale jeśli chodzi o setTimeoutmetodę, staje się wykonaniem asynchronicznym. Ale JS natychmiast wraca z niego i kontynuuje wykonywanie, co nazywa się Non-Blockinglub Async. I kontynuuje pracę nad innymi itp.

Wyniki tego wykonania są następujące:

acdb

W zasadzie druga setTimeoutzostała ukończona jako pierwsza, a jej funkcja zwrotna jest wykonywana wcześniej niż pierwsza i to ma sens.

Mowa tutaj o aplikacji jednowątkowej. JS Engine kontynuuje to i dopóki nie zakończy pierwszego żądania, nie przejdzie do drugiego. Ale dobrą rzeczą jest to, że nie będzie czekać na operacje blokujące, takie jak setTimeoutrozwiązanie, więc będzie szybsze, ponieważ akceptuje nowe przychodzące żądania.

Ale moje pytania dotyczą następujących elementów:

# 1: Jeśli mówimy o aplikacji jednowątkowej, to jaki mechanizm przetwarza, setTimeoutspodczas gdy silnik JS przyjmuje więcej żądań i wykonuje je? W jaki sposób pojedynczy wątek kontynuuje pracę z innymi żądaniami? Co działa, setTimeoutgdy inne żądania wciąż przychodzą i są wykonywane.

# 2: Jeśli te setTimeoutfunkcje są wykonywane za kulisami, podczas gdy więcej żądań przychodzi i jest wykonywanych, co wykonuje asynchroniczne wykonania za kulisami? Co to za rzecz, o której mówimy, zwana EventLoop?

# 3: Ale czy nie powinno się umieszczać całej metody w EventLooptak, aby całość została wykonana i wywołana została metoda wywołania zwrotnego? Oto, co rozumiem, mówiąc o funkcjach wywołania zwrotnego:

function downloadFile(filePath, callback)
{
   blah.downloadFile(filePath);
   callback();
}

Ale w tym przypadku, skąd silnik JS wie, czy jest to funkcja asynchroniczna, aby mógł umieścić wywołanie zwrotne w EventLoop? Być może coś w rodzaju asyncsłowa kluczowego w C # lub jakiegoś rodzaju atrybutu wskazującego, że metoda, którą zastosuje JS Engine, jest metodą asynchroniczną i powinno być odpowiednio traktowane.

# 4: Ale artykuł mówi całkiem przeciwnie do tego, co przypuszczałem na temat tego, jak może działać:

Pętla zdarzeń to kolejka funkcji zwrotnych. Podczas wykonywania funkcji asynchronicznej funkcja zwrotna jest umieszczana w kolejce. Silnik JavaScript nie rozpoczyna przetwarzania pętli zdarzeń, dopóki kod nie zostanie wykonany po wykonaniu funkcji asynchronicznej.

# 5: I jest tutaj ten obraz, który może być pomocny, ale pierwsze wyjaśnienie na obrazku mówi dokładnie to samo, co w pytaniu numer 4:

wprowadź opis obrazu tutaj

Więc moje pytanie ma na celu uzyskanie wyjaśnień na temat wymienionych powyżej pozycji?

Tarik
źródło
1
Wątki nie są właściwą metaforą do rozwiązywania tych problemów. Pomyśl o wydarzeniach.
Denys Séguret
1
@dystroy: Przyjemnie byłoby zobaczyć przykładowy kod ilustrujący metaforę zdarzenia w JS.
Tarik
Nie rozumiem, o co dokładnie chodzi tutaj.
Denys Séguret
1
@dystroy: Moje pytanie ma na celu uzyskanie wyjaśnień dotyczących elementów wymienionych powyżej?
Tarik
2
Node nie jest jednowątkowy, ale nie ma to dla Ciebie znaczenia (poza faktem, że radzi sobie z innymi rzeczami podczas wykonywania kodu użytkownika). Tylko jedno wywołanie zwrotne w kodzie użytkownika jest wykonywane jednocześnie.
Denys Séguret

Odpowiedzi:

86

1: Jeśli mówimy o aplikacji jednowątkowej, to jakie procesy setTimeouts, podczas gdy silnik JS przyjmuje więcej żądań i wykonuje je? Czy ten pojedynczy wątek nie będzie kontynuował pracy nad innymi żądaniami? Kto będzie dalej pracował nad setTimeout, podczas gdy inne żądania będą przychodzić i być wykonywane.

W procesie węzła jest tylko jeden wątek, który faktycznie wykona JavaScript twojego programu. Jednak w samym węźle istnieje kilka wątków obsługujących operację mechanizmu pętli zdarzeń, w tym pulę wątków we / wy i kilka innych. Kluczem jest liczba tych wątków, która nie odpowiada liczbie współbieżnych połączeń obsługiwanych tak, jak w modelu współbieżności wątków na połączenie.

Jeśli chodzi o „wykonywanie setTimeouts”, kiedy wywołujesz setTimeout, wszystko, co robi węzeł, to po prostu aktualizowanie struktury danych funkcji, które mają być wykonywane w przyszłości. Zasadniczo ma kilka kolejek rzeczy, które trzeba wykonać, a każde „tik” pętli zdarzeń wybiera jeden, usuwa go z kolejki i uruchamia.

Kluczową rzeczą do zrozumienia jest to, że węzeł opiera się na systemie operacyjnym w większości przypadków podnoszenia ciężarów. Tak więc przychodzące żądania sieciowe są w rzeczywistości śledzone przez sam system operacyjny, a gdy węzeł jest gotowy do obsługi jednego z nich, po prostu używa wywołania systemowego, aby poprosić system operacyjny o żądanie sieciowe z danymi gotowymi do przetworzenia. Tak duża część „pracy” węzła we / wy to „Hej OS, masz połączenie sieciowe z danymi gotowymi do odczytu?” lub „Hej OS, czy któreś z moich zaległych wywołań systemu plików mają gotowe dane?”. Bazując na swoim wewnętrznym algorytmie i konstrukcji mechanizmu pętli zdarzeń, node wybierze jeden „tik” JavaScript do wykonania, uruchomi go, a następnie powtórzy proces od nowa. To właśnie oznacza pętla zdarzeń. Węzeł zasadniczo cały czas określa, „jaki jest następny fragment JavaScript, który powinienem uruchomić?”, A następnie go uruchamia.setTimeoutlub process.nextTick.

2: Jeśli te setTimeout zostaną wykonane za kulisami, podczas gdy więcej żądań przychodzi i jest wykonywanych, to rzecz, która wykonuje asynchroniczne wykonania za kulisami, to to, że mówimy o EventLoop?

Żaden JavaScript nie jest wykonywany za kulisami. Cały JavaScript w twoim programie działa z przodu i na środku, pojedynczo. To, co dzieje się za kulisami, to obsługa operacji we / wy przez system operacyjny, a węzeł czeka, aż będzie gotowy, a węzeł zarządza kolejką javascript czekającą na wykonanie.

3: Skąd JS Engine może wiedzieć, czy jest to funkcja asynchroniczna, aby mógł umieścić ją w EventLoop?

Istnieje ustalony zestaw funkcji w rdzeniu węzła, które są asynchroniczne, ponieważ wykonują wywołania systemowe, a węzeł wie, które to są, ponieważ muszą wywoływać system operacyjny lub C ++. Zasadniczo wszystkie interakcje we / wy sieci i systemu plików, a także procesy potomne będą asynchroniczne, a JEDYNYM sposobem, w jaki JavaScript może sprawić, by węzeł działał asynchronicznie, jest wywołanie jednej z funkcji asynchronicznych udostępnianych przez bibliotekę rdzenia węzła. Nawet jeśli używasz pakietu npm, który definiuje swój własny interfejs API, w celu uzyskania pętli zdarzeń, ostatecznie kod pakietu npm wywoła jedną z funkcji asynchronicznych węzła core i wtedy węzeł wie, że zaznaczenie jest zakończone i może rozpocząć zdarzenie ponownie algorytm pętli.

4 Pętla zdarzeń to kolejka funkcji zwrotnych. Podczas wykonywania funkcji asynchronicznej funkcja zwrotna jest umieszczana w kolejce. Silnik JavaScript nie rozpoczyna przetwarzania pętli zdarzeń, dopóki kod nie zostanie wykonany po wykonaniu funkcji asynchronicznej.

Tak, to prawda, ale to mylące. Kluczową rzeczą jest to, że normalny wzór to:

//Let's say this code is running in tick 1
fs.readFile("/home/barney/colors.txt", function (error, data) {
  //The code inside this callback function will absolutely NOT run in tick 1
  //It will run in some tick >= 2
});
//This code will absolutely also run in tick 1
//HOWEVER, typically there's not much else to do here,
//so at some point soon after queueing up some async IO, this tick
//will have nothing useful to do so it will just end because the IO result
//is necessary before anything useful can be done

Więc tak, możesz całkowicie zablokować pętlę zdarzeń, po prostu zliczając liczby Fibonacciego synchronicznie wszystkie w pamięci w tym samym ticku, i tak, to całkowicie zawiesiłoby twój program. To współbieżność kooperacyjna. Każdy tik JavaScript musi spowodować pętlę zdarzeń w rozsądnym czasie, w przeciwnym razie ogólna architektura ulegnie awarii.

Peter Lyons
źródło
1
Powiedzmy, że mam kolejkę, której wykonanie zajmie serwerowi 1 minutę, a pierwszą rzeczą była funkcja asynchroniczna, która zakończyła się po 10 sekundach. Czy przejdzie do końca kolejki, czy też wepchnie się w linię, gdy będzie gotowa?
ilyo
4
Zwykle trafia na koniec kolejki, ale semantyka process.nextTickvs setTimeoutvs setImmediatejest nieco inna, chociaż nie powinieneś się tym przejmować. Mam post na blogu o nazwie setTimeout i znajomych, który zawiera więcej szczegółów.
Peter Lyons
Czy możesz to rozwinąć? Powiedzmy, że mam dwa wywołania zwrotne, a pierwszy ma metodę changeColor z czasem wykonania 10 ms i setTimeout równą 1 minutę, a drugi ma metodę changeBackground z czasem wykonania 50 ms z setTimeout równym 10 sekund. Czuję, że changeBackground będzie najpierw w kolejce, a changeColor będzie następny. Następnie pętla Event synchronicznie wybiera metody. Czy mam rację?
SheshPai
1
@SheshPai jest zbyt mylące, aby każdy mógł dyskutować o kodzie napisanym akapitami po angielsku. Po prostu opublikuj nowe pytanie z fragmentem kodu, aby ludzie mogli odpowiedzieć na podstawie kodu zamiast opisu kodu, co pozostawia wiele niejasności.
Peter Lyons
youtube.com/watch?v=QyUFheng6J0&spfreload=5 to kolejne dobre wyjaśnienie silnika JavaScript
Mukesh Kumar,
67

Jest fantastyczny samouczek wideo autorstwa Philipa Robertsa, który wyjaśnia pętlę zdarzeń javascript w najbardziej uproszczony i koncepcyjny sposób. Każdy programista javascript powinien to sprawdzić.

Oto link wideo na Youtube.

sam100rav
źródło
17
Oglądałem to i było to rzeczywiście najlepsze wytłumaczenie w historii.
Tarik
1
Film obowiązkowy dla fanów i entuzjastów JavaScript.
Nirus
1
ten film zmienia moje życie ^^
HuyTran
1
przyszedłem tu wędrować .. i to jest jedno z najlepszych wyjaśnień jakie otrzymałem .. dziękuję za udostępnienie ...: D
RohitS
1
To był nowicjusz
HebleV
11

Nie myśl, że proces hosta jest jednowątkowy, tak nie jest. To, co jest jednowątkowe, to część procesu hosta, która wykonuje kod javascript.

Z wyjątkiem pracowników w tle , ale to komplikuje scenariusz ...

Tak więc cały twój kod js działa w tym samym wątku i nie ma możliwości, aby dwie różne części kodu js działały równolegle (więc nie masz możliwości zarządzania nigthmare współbieżności).

Wykonywany kod js jest ostatnim kodem odebranym przez proces hosta z pętli zdarzeń. W swoim kodzie możesz zasadniczo zrobić dwie rzeczy: uruchamiać instrukcje synchroniczne i planować wykonanie funkcji w przyszłości, gdy wystąpią jakieś zdarzenia.

Oto moja reprezentacja mentalna (uwaga: po prostu nie znam szczegółów implementacji przeglądarki!) Twojego przykładowego kodu:

console.clear();                                   //exec sync
console.log("a");                                  //exec sync
setTimeout(                //schedule inAWhile to be executed at now +1 s 
    function inAWhile(){
        console.log("b");
    },1000);    
console.log("c");                                  //exec sync
setTimeout(
    function justNow(){          //schedule justNow to be executed just now
        console.log("d");
},0);       

Gdy Twój kod jest uruchomiony, inny wątek w procesie hosta śledzi wszystkie występujące zdarzenia systemowe (kliknięcia interfejsu użytkownika, odczyt plików, otrzymane pakiety sieciowe itp.)

Po zakończeniu kodu jest usuwany z pętli zdarzeń, a proces hosta wraca do sprawdzania go, aby sprawdzić, czy jest więcej kodu do uruchomienia. Pętla zdarzeń zawiera więcej dwóch programów obsługi zdarzeń: jedną do wykonania teraz (funkcja justNow), a drugą w ciągu sekundy (funkcja inAWhile).

Proces hosta próbuje teraz dopasować wszystkie zdarzenia, aby sprawdzić, czy są zarejestrowane dla nich programy obsługi. Okazało się, że zdarzenie, na które czeka justNow, miało miejsce, więc zaczął uruchamiać swój kod. Kiedy funkcja justNow kończy działanie, ponownie sprawdza pętlę zdarzeń, szukając programów obsługi zdarzeń. Zakładając, że minęło 1 s, uruchamia funkcję inAWhile i tak dalej ....

Andrea Parodi
źródło
Jednak setTimeout jest zaimplementowany w głównym wątku. W twoim przykładzie nie ma więc nic, co wymagałoby oddzielnego wątku. W rzeczywistości w przeglądarkach tylko karty są zaimplementowane w wielu wątkach. Na jednej karcie wszystkie procesy, w tym tworzenie wielu równoległych połączeń sieciowych, oczekiwanie na kliknięcia myszą, ustawianie czasu oczekiwania, animacje itp., Są wykonywane w tym samym wątku
slebetman