2016-01-16 21 views
5

Poniższy przykład jest podany w książce node.js:Dlaczego pętla while blokuje pętlę zdarzeń węzła?

var open = false; 

setTimeout(function() { 
    open = true 
}, 1000) 

while (!open) { 
    console.log('wait'); 
} 

console.log('open sesame'); 

Wyjaśniając, dlaczego podczas blokuje wykonanie pętli, autor mówi:

Węzeł nigdy wykonać zwrotnego limit czasu, ponieważ pętli zdarzeń jest utknął na tym, podczas gdy pętla rozpoczęła się na linii 7, nigdy nie dając jej szansy , aby przetworzyć event timeout!

Jednak autor nie wyjaśnia, dlaczego tak się dzieje w kontekście pętli zdarzeń lub co naprawdę dzieje się pod maską.

Czy ktoś może to wyjaśnić? Dlaczego węzeł utknął? I jak zmieniłby powyższy kod, zachowując strukturę kontrolną while, tak aby pętla zdarzeń nie była blokowana i kod będzie zachowywał się tak, jak można by się spodziewać; czekać będzie zalogowany przez 1 sekundę przed ogniem setTimeout, a następnie proces kończy się po zalogowaniu "open sezam".

Ogólne wyjaśnienia, takie jak odpowiedzi to this question o pętli IO oraz pętli zdarzeń i wywołań zwrotnych nie pomagają mi zracjonalizować tego. Mam nadzieję, że pomoże mi odpowiedź, która bezpośrednio odwołuje się do powyższego kodu.

+1

Możliwy duplikat [W jaki sposób działa unikalny blokujący model IO w pliku Node.js] (http://stackoverflow.com/questions/14795145/how-the-single-threaded-non- blocking-io-model-works-in-node-js) – thefourtheye

+1

Nie zgadzam się, że jest to duplikat. Odwołuje się do konkretnego fragmentu kodu i konkretnego wyjaśnienia tego fragmentu kodu. Pyta również, w jaki sposób można przepisać kod. – codecowboy

+0

Nie ma problemu :-) Mówi się, że jest to * możliwy * duplikat, a nie duplikat. – thefourtheye

Odpowiedz

10

To naprawdę proste. Wewnętrznie node.js składa tego rodzaju pętli:

  • coś z kolejki zdarzeń
  • Run cokolwiek zadaniem jest wskazany i uruchomić go, aż powróci
  • Gdy powyższe zadanie zostało wykonane, uzyskać Kolejnym punktem z kolejki zdarzeń
  • Run cokolwiek zadaniem jest wskazany i uruchomić go, aż powróci
  • opłukać, spienić, powtórz - kółko

Jeśli w którymś momencie nie ma nic w kolejce zdarzeń, to idź spać, dopóki coś nie zostanie umieszczone w kolejce zdarzeń.


Tak więc, jeśli kawałek JavaScript siedzi w while() pętli, to zadanie nie jest wykończenie i za powyższej sekwencji, nic nowego zostaną zabrani z kolejki zdarzeń aż że przed zadaniem jest całkowicie zrobione . Tak więc, bardzo długa lub na zawsze działająca pętla po prostu dzwoni do roboty. Ponieważ JavaScript uruchamia tylko jedno zadanie na raz (pojedynczy wątek dla wykonania JS), jeśli to jedno zadanie wiruje w pętli while, to nic innego nie może nigdy wykonać.

Oto prosty przykład, który może pomóc wyjaśnić:

var done = false; 

// set a timer for 1 second from now to set done to true 
setTimeout(function() { 
     done = true; 
}, 1000); 

// spin wait for the done value to change 
while (!done) { /* do nothing */} 

console.log("finally, the done value changed!"); 

Niektórzy mogą logicznie myśleć, że pętla while będzie spin aż pożarów timera a następnie zegar zmieni wartość done do true a następnie natomiast pętla zakończy się, a na końcu zostanie wykonany console.log(). NIE JEST to, co się stanie. Będzie to rzeczywiście nieskończona pętla, a instrukcja console.log() nigdy nie zostanie wykonana.

Problem polega na tym, że po przejściu do trybu oczekiwania na obrót w pętli while() NIE MOŻNA wykonywać żadnych innych skryptów JavaScript. Tak więc timer, który chce zmienić wartość zmiennej done, nie może wykonać. Zatem warunki pętli while nigdy się nie zmieniają, a zatem jest to nieskończona pętla.

Oto co się dzieje wewnętrznie wewnątrz silnika JS:

  1. done zmienna zainicjowana do false
  2. setTimeout() harmonogramów zdarzenie timera przez 1 sekundę od teraz
  3. czas pętla zaczyna przędzenia
  4. 1 sekunda do wirowania pętli while, timer odpala wewnętrznie do silnika JS, a wywołanie zwrotne timera jest dodawane do kolejki zdarzeń. To prawdopodobnie występuje w innym wątku, wewnętrznym w silniku JS.
  5. Pętla while wciąż się kręci, ponieważ zmienna done nigdy się nie zmienia. Ponieważ nadal się kręci, silnik JS nigdy nie kończy tego wątku wykonywania i nigdy nie wyciąga następnego elementu z kolejki zdarzeń.
+0

dzięki @ jfriend00. To wspaniała odpowiedź i to, czego szukałem. Mam nadzieję, że inni też to zrobią, nie dlatego, że potrzebujesz przedstawiciela;) – codecowboy

+0

Tak więc mam bardzo długi przebieg wokół buszu, który podczas gdy pętle blokują się w js. –

+0

@marshalcraft - DOWOLNA pętla w blokach Javascript.JavaScript jest jednym wątkiem i sterowany zdarzeniami, tak długo jak działa pętla, nie można uruchamiać niczego poza kodem w pętli. I to nie tylko pętle. Każda długa funkcja uniemożliwia działanie innych rzeczy. Pojedynczy wątek - JavaScript może wykonywać tylko jedną synchronizację naraz. Wszystkie pętle są synchroniczne. Jeśli go przegapiłeś, podsumowanie znajduje się w trzecim akapicie, po wyjaśnieniu, jak działa kolejka zdarzeń (co jest kluczem do zrozumienia tego wszystkiego). Nie sądzę, że to bardzo długi okres w buszu. – jfriend00

2

Węzeł jest pojedynczym zadaniem szeregowym. Nie ma równoległości, a jej współbieżność jest związana z IO. Pomyśl o tym w ten sposób: wszystko działa na jednym wątku, gdy wykonujesz wywołanie we/wy, które blokuje/synchronizuje twój proces zatrzymuje się, dopóki dane nie zostaną zwrócone; jednak powiedzmy, że mamy jeden wątek, który zamiast czekać na IO (odczyt dysku, chwytanie adresu URL itp.), twoje zadanie przechodzi do następnego zadania, a po wykonaniu tego zadania sprawdza to IO. Jest to w zasadzie to, co robi węzeł, a jego "pętla zdarzeń" to polling IO do wypełnienia (lub postępu) w pętli. Tak więc, gdy zadanie nie zostanie ukończone (twoja pętla), pętla zdarzeń nie będzie się rozwijać. Mówiąc po prostu.

+2

@codecowboy W Node.js istnieje tylko jeden główny wątek. Zatem cały kod musi być wykonany tylko przez ten wątek. 'SetTimeout' zaplanuje wykonanie funkcji po 1 sekundzie. Więc nie wykona funkcji natychmiast i przejdzie do następnej instrukcji wykonywalnej. Znajduje pętlę 'while'. Ponieważ jest to pojedynczy wątek, tylko wtedy, gdy bieżące zadanie jest zakończone, Node.js może wybrać następny element z kolejki. Ale wtedy 'while' działa w nieskończoność, więc funkcja zarejestrowana przez' setTimeout' nigdy nie ma szansy na wykonanie. – thefourtheye

1

To jest świetne pytanie, ale znalazłem poprawkę!

var sleep = require('system-sleep') 
var done = false 

setTimeout(function() { 
    done = true 
}, 1000) 

while (!done) { 
    sleep(100) 
    console.log('sleeping') 
} 

console.log('finally, the done value changed!') 

Myślę, że to działa, ponieważ system-sleep nie jest oczekiwaniem na wirowanie.

+0

Zastanawiam się, nikt nie przegłosował jedynego rozwiązania na tej stronie, które po prostu działa, poza wszystkimi rozmowami z PHD. – jolly

+0

Później przekonałem się, że jest to jedyne rozwiązanie, które działa, ALE działa tylko dlatego, że jest to hak C++, nie powinno to być możliwe w Node.js - będzie działać na większości platform, ale na niektórych platformach się nie powiedzie. więc należy go używać z rozwagą - jednak jest to świetne rozwiązanie dla deweloperów – danday74

0

ponieważ timer musi powrócić i czeka na zakończenie pętli, aby dodać ją do kolejki, więc chociaż timeout znajduje się w osobnym wątku i rzeczywiście może sfinalizować timer, ale "zadanie" do ustawienia done = true czeka na tej nieskończonej pętli do końca

Powiązane problemy