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 setTimeout
Methoden geht, wird es eine asynchrone Ausführung. Aber JS kehrt sofort davon zurück und setzt die Ausführung fort, die als Non-Blocking
oder 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 setTimeout
zuerst 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, setTimeouts
während die JS-Engine mehr Anforderungen akzeptiert und ausführt? Wie arbeitet der einzelne Thread weiter an anderen Anforderungen? Was funktioniert, setTimeout
während andere Anfragen immer wieder eingehen und ausgeführt werden ?
# 2: Wenn diese setTimeout
Funktionen 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, EventLoop
damit 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 the
Schlü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:
Meine Frage hier ist also, um einige Klarstellungen zu den oben aufgeführten Punkten zu erhalten?
Antworten:
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.
setTimeout
oderprocess.nextTick
.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.
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.
Ja, das ist wahr, aber es ist irreführend. Der Schlüssel zum normalen Muster ist:
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.
quelle
process.nextTick
vssetTimeout
vssetImmediate
ist subtil unterschiedlich, obwohl Sie sich eigentlich nicht darum kümmern sollten. Ich habe einen Blog-Beitrag namens setTimeout und Freunde , der detaillierter geht.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.
quelle
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!):
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 ....
quelle