Grundlegendes zur Ereignisschleife

134

Ich denke darüber nach und habe mir Folgendes ausgedacht:

Nehmen wir an, wir haben einen Code wie diesen:

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

Eine Anfrage kommt herein und die JS-Engine beginnt Schritt für Schritt mit der Ausführung des obigen Codes. Die ersten beiden Anrufe sind Synchronisierungsanrufe. Aber wenn es um setTimeoutMethoden geht, wird es eine asynchrone Ausführung. Aber JS kehrt sofort davon zurück und setzt die Ausführung fort, die als Non-Blockingoder bezeichnet wird Async. Und es arbeitet weiter an anderen etc.

Das Ergebnis dieser Ausführung ist das Folgende:

acdb

Im Grunde genommen wurde die zweite setTimeoutzuerst beendet und ihre Rückruffunktion wird früher als die erste ausgeführt, und das ist sinnvoll.

Wir sprechen hier über Single-Threaded-Anwendung. JS Engine führt dies weiterhin aus. Wenn die erste Anforderung nicht abgeschlossen ist, wird die zweite Anforderung nicht ausgeführt. Das Gute ist jedoch, dass es nicht auf das Auflösen von Blockierungsvorgängen wartet setTimeout, sodass es schneller ist, da es die neuen eingehenden Anforderungen akzeptiert.

Meine Fragen stellen sich jedoch zu folgenden Punkten:

# 1: Wenn es sich um eine Single-Threaded-Anwendung handelt, welcher Mechanismus wird dann verarbeitet, setTimeoutswährend die JS-Engine mehr Anforderungen akzeptiert und ausführt? Wie arbeitet der einzelne Thread weiter an anderen Anforderungen? Was funktioniert, setTimeoutwährend andere Anfragen immer wieder eingehen und ausgeführt werden ?

# 2: Wenn diese setTimeoutFunktionen hinter den Kulissen ausgeführt werden, während weitere Anforderungen eingehen und ausgeführt werden, was führt die asynchrone Ausführung hinter den Kulissen aus? Was ist das für ein Ding, über das wir sprechen EventLoop?

# 3: Aber sollte nicht die gesamte Methode in die Datei eingefügt werden, EventLoopdamit das Ganze ausgeführt und die Rückrufmethode aufgerufen wird? Folgendes verstehe ich, wenn ich über Rückruffunktionen spreche:

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

In diesem Fall weiß die JS Engine jedoch, ob es sich um eine asynchrone Funktion handelt, sodass sie den Rückruf in das EventLoop? Perhaps something like theSchlüsselwort async` in C # oder in ein Attribut einfügen kann, das angibt, dass die Methode, die JS Engine übernimmt, eine asynchrone Methode ist und sollte entsprechend behandelt werden.

# 4: Aber ein Artikel sagt ganz im Gegensatz zu dem, was ich vermutet habe, wie die Dinge funktionieren könnten:

Die Ereignisschleife ist eine Warteschlange von Rückruffunktionen. Wenn eine asynchrone Funktion ausgeführt wird, wird die Rückruffunktion in die Warteschlange gestellt. Die JavaScript-Engine beginnt erst mit der Verarbeitung der Ereignisschleife, wenn der Code nach Ausführung einer asynchronen Funktion ausgeführt wurde.

# 5: Und es gibt dieses Bild hier, das hilfreich sein könnte, aber die erste Erklärung im Bild sagt genau dasselbe, was in Frage 4 erwähnt wurde:

Geben Sie hier die Bildbeschreibung ein

Meine Frage hier ist also, um einige Klarstellungen zu den oben aufgeführten Punkten zu erhalten?

Tarik
quelle
1
Threads sind nicht die richtige Metapher, um diese Probleme zu lösen. Denken Sie an Ereignisse.
Denys Séguret
1
@dystroy: Ein Codebeispiel zur Veranschaulichung dieser Ereignismetapher in JS wäre schön zu sehen.
Tarik
Ich verstehe nicht, was genau Ihre Frage hier ist.
Denys Séguret
1
@dystroy: Meine Frage hier ist, um einige Klarstellungen zu den oben aufgeführten Elementen zu erhalten?
Tarik
2
Der Knoten ist kein Single-Thread, aber das spielt für Sie keine Rolle (abgesehen von der Tatsache, dass er es schafft, andere Dinge zu erledigen, während Ihr Benutzercode ausgeführt wird). Es wird höchstens ein Rückruf in Ihrem Benutzercode gleichzeitig ausgeführt.
Denys Séguret

Antworten:

85

1: Wenn es sich um eine Single-Threaded-Anwendung handelt, welche Prozesse setzen dann setTimeouts, während die JS-Engine mehr Anforderungen akzeptiert und ausführt? Wird dieser einzelne Thread nicht weiter an anderen Anforderungen arbeiten? Wer wird dann weiter an setTimeout arbeiten, während andere Anfragen kommen und ausgeführt werden?

Es gibt nur einen Thread im Knotenprozess, der das JavaScript Ihres Programms tatsächlich ausführt. Innerhalb des Knotens selbst gibt es jedoch tatsächlich mehrere Threads, die den Ereignisschleifenmechanismus verarbeiten, und dies umfasst einen Pool von E / A-Threads und eine Handvoll anderer. Der Schlüssel ist, dass die Anzahl dieser Threads nicht der Anzahl der gleichzeitigen Verbindungen entspricht, die wie in einem Parallelitätsmodell für Threads pro Verbindung behandelt werden.

Wenn Sie nun "setTimeouts ausführen" aufrufen setTimeout, aktualisieren Sie beim Aufrufen lediglich eine Datenstruktur von Funktionen, die zu einem späteren Zeitpunkt ausgeführt werden sollen. Es hat im Grunde eine Reihe von Warteschlangen mit Dingen, die erledigt werden müssen, und jedes "Häkchen" der Ereignisschleife, die es auswählt, entfernt es aus der Warteschlange und führt es aus.

Es ist wichtig zu verstehen, dass sich der Knoten für den Großteil des schweren Hebens auf das Betriebssystem verlässt. Eingehende Netzwerkanforderungen werden also tatsächlich vom Betriebssystem selbst verfolgt. Wenn der Knoten bereit ist, eine zu verarbeiten, verwendet er nur einen Systemaufruf, um das Betriebssystem nach einer Netzwerkanforderung mit Daten zu fragen, die zur Verarbeitung bereit sind. So viel vom E / A-Arbeitsknoten ist entweder "Hey OS, haben Sie eine Netzwerkverbindung mit lesbaren Daten?" oder "Hey OS, hat einer meiner ausstehenden Dateisystemaufrufe Daten bereit?". Basierend auf seinem internen Algorithmus und dem Design der Ereignisschleifen-Engine wählt der Knoten ein "Häkchen" von JavaScript aus, führt es aus und wiederholt den Vorgang erneut. Das ist es, was mit der Ereignisschleife gemeint ist. Der Knoten bestimmt grundsätzlich jederzeit "Was ist das nächste kleine Stück JavaScript, das ich ausführen soll?" Und führt es dann aus.setTimeoutoder process.nextTick.

2: Wenn diese setTimeout hinter den Kulissen ausgeführt werden, während weitere Anforderungen eingehen und eingehen und ausgeführt werden, ist die Sache, die die asynchronen Ausführungen hinter den Kulissen ausführt, die, über die wir sprechen, EventLoop?

Hinter den Kulissen wird kein JavaScript ausgeführt. Das gesamte JavaScript in Ihrem Programm wird einzeln vorne und in der Mitte ausgeführt. Was hinter den Kulissen passiert, ist, dass das Betriebssystem E / A verarbeitet und der Knoten darauf wartet, dass dies bereit ist, und dass der Knoten seine Javascript-Warteschlange verwaltet, die auf die Ausführung wartet.

3: Wie kann JS Engine wissen, ob es sich um eine asynchrone Funktion handelt, damit sie in die EventLoop aufgenommen werden kann?

Es gibt einen festen Satz von Funktionen im Knotenkern, die asynchron sind, weil sie Systemaufrufe ausführen, und der Knoten weiß, welche dies sind, weil sie das Betriebssystem oder C ++ aufrufen müssen. Grundsätzlich sind alle Netzwerk- und Dateisystem-E / A- sowie untergeordneten Prozessinteraktionen asynchron. Die einzige Möglichkeit, wie JavaScript den Knoten dazu bringen kann, etwas asynchron auszuführen, besteht darin, eine der von der Knotenkernbibliothek bereitgestellten asynchronen Funktionen aufzurufen. Selbst wenn Sie ein npm-Paket verwenden, das seine eigene API definiert, ruft der Code des npm-Pakets eine der asynchronen Funktionen des Knotenkerns auf, um die Ereignisschleife zu erhalten. In diesem Fall weiß der Knoten, dass das Häkchen vollständig ist, und kann das Ereignis starten wieder Schleifenalgorithmus.

4 Die Ereignisschleife ist eine Warteschlange mit Rückruffunktionen. Wenn eine asynchrone Funktion ausgeführt wird, wird die Rückruffunktion in die Warteschlange gestellt. Die JavaScript-Engine beginnt erst mit der Verarbeitung der Ereignisschleife, wenn der Code nach Ausführung einer asynchronen Funktion ausgeführt wurde.

Ja, das ist wahr, aber es ist irreführend. Der Schlüssel zum normalen Muster ist:

//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

Also ja, Sie könnten die Ereignisschleife vollständig blockieren, indem Sie einfach alle Fibonacci-Zahlen synchron im Speicher alle im selben Tick zählen, und ja, das würde Ihr Programm vollständig einfrieren. Es ist kooperative Parallelität. Jeder Tick von JavaScript muss die Ereignisschleife innerhalb eines angemessenen Zeitraums ergeben, da sonst die Gesamtarchitektur ausfällt.

Peter Lyons
quelle
1
Nehmen wir an, ich habe eine Warteschlange, deren Ausführung 1 Minute dauert, und das erste war eine asynchrone Funktion, die nach 10 Sekunden beendet wurde. Wird es bis zum Ende der Warteschlange gehen oder wird es sich in die Zeile schieben, sobald es bereit ist?
Ilyo
4
Im Allgemeinen wird es bis zum Ende der Warteschlange gehen, aber die Semantik von process.nextTickvs setTimeoutvs setImmediateist subtil unterschiedlich, obwohl Sie sich eigentlich nicht darum kümmern sollten. Ich habe einen Blog-Beitrag namens setTimeout und Freunde , der detaillierter geht.
Peter Lyons
Können Sie bitte näher darauf eingehen? Angenommen, ich habe zwei Rückrufe und der erste hat eine changeColor-Methode mit einer Ausführungszeit von 10 ms und einem setTimeout von 1 Minute und der zweite eine changeBackground-Methode mit einer Ausführungszeit von 50 ms mit einem setTimeout von 10 Sekunden. Ich habe das Gefühl, dass der changeBackground zuerst in der Warteschlange steht und der changeColor als nächstes. Danach wählt die Ereignisschleife die Methoden synchron aus. Habe ich recht?
SheshPai
1
@SheshPai Es ist zu verwirrend für jeden, Code zu diskutieren, wenn er in englischen Absätzen geschrieben ist. Stellen Sie einfach eine neue Frage mit einem Code-Snippet, damit die Benutzer basierend auf Code anstelle einer Beschreibung des Codes antworten können, was viel Unklarheit hinterlässt.
Peter Lyons
youtube.com/watch?v=QyUFheng6J0&spfreload=5 Dies ist eine weitere gute Erklärung der JavaScript Engine
Mukesh Kumar
65

Es gibt ein fantastisches Video-Tutorial von Philip Roberts, das die Javascript-Ereignisschleife auf einfachste und konzeptionellste Weise erklärt. Jeder Javascript-Entwickler sollte einen Blick darauf werfen.

Hier ist der Videolink auf Youtube.

sam100rav
quelle
16
Ich habe es gesehen und es war in der Tat die beste Erklärung überhaupt.
Tarik
1
Ein Muss für JavaScript-Fans und Enthusiasten.
Nirus
1
Dieses Video verändert mein Leben ^^
HuyTran
1
kam hierher wandern .. und dies ist eine der besten Erklärungen, die ich bekommen habe .. danke für das Teilen ...: D
RohitS
1
Das war ein Augenöffner
HebleV
11

Denken Sie nicht, dass der Host-Prozess Single-Threaded ist, das sind sie nicht. Single-Threaded ist der Teil des Host-Prozesses, der Ihren Javascript-Code ausführt.

Mit Ausnahme von Hintergrundarbeitern , aber diese erschweren das Szenario ...

Ihr gesamter js-Code wird also im selben Thread ausgeführt, und es besteht keine Möglichkeit, dass zwei verschiedene Teile Ihres js-Codes gleichzeitig ausgeführt werden (Sie müssen also keine Nigthmare zur gleichzeitigen Verwaltung verwalten).

Der ausgeführte js-Code ist der letzte Code, den der Host-Prozess aus der Ereignisschleife abgerufen hat. In Ihrem Code können Sie grundsätzlich zwei Dinge tun: synchrone Anweisungen ausführen und Funktionen planen, die in Zukunft ausgeführt werden sollen, wenn einige Ereignisse eintreten.

Hier ist meine mentale Darstellung Ihres Beispielcodes (Vorsicht: Ich kenne nur die Details der Browser-Implementierung nicht!):

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);       

Während Ihr Code ausgeführt wird, verfolgt ein anderer Thread im Host-Prozess alle auftretenden Systemereignisse (Klicks auf die Benutzeroberfläche, gelesene Dateien, empfangene Netzwerkpakete usw.).

Wenn Ihr Code abgeschlossen ist, wird er aus der Ereignisschleife entfernt, und der Host-Prozess überprüft ihn erneut, um festzustellen, ob mehr Code ausgeführt werden muss. Die Ereignisschleife enthält zwei weitere Ereignishandler: einen, der jetzt ausgeführt werden soll (die justNow-Funktion), und einen innerhalb einer Sekunde (die inAWhile-Funktion).

Der Host-Prozess versucht nun, alle Ereignisse abzugleichen, um festzustellen, ob dort Handler für sie registriert sind. Es wurde festgestellt, dass das Ereignis, auf das justNow wartet, eingetreten ist, sodass der Code ausgeführt wird. Wenn die Funktion justNow beendet wird, wird die Ereignisschleife ein weiteres Mal überprüft und nach Handlern für Ereignisse gesucht. Angenommen, 1 s ist vergangen, wird die Funktion inAWhile ausgeführt und so weiter ....

Andrea Parodi
quelle