Heim > Web-Frontend > js-Tutorial > Hauptteil

Detaillierte Erläuterung des Node.js-Ereignisschleifen-Workflows und -Lebenszyklus

不言
Freigeben: 2018-08-15 14:26:47
Original
2221 Leute haben es durchsucht

Dieser Artikel bietet Ihnen eine detaillierte Erklärung des Ereignisschleifen-Workflows und des Lebenszyklus von Node.js. Ich hoffe, dass er für Freunde hilfreich ist.

In diesem Artikel werden der Workflow und der Lebenszyklus der Ereignisschleife von node.js ausführlich erläutert

Einige häufige Missverständnisse

in js Das Ereignis Schleife innerhalb der Engine

Eines der häufigsten Missverständnisse ist, dass die Ereignisschleife Teil der Javascript-Engine (V8, SpiderMonkey usw.) ist. Tatsächlich verwendet die Ereignisschleife hauptsächlich die Javascript-Engine, um Code auszuführen.

Es gibt einen Stapel oder eine Warteschlange

Erstens gibt es keinen Stapel, da es mehrere Warteschlangen gibt (wie Warteschlangen in der Datenstruktur). ) beteiligt. Die meisten Entwickler wissen jedoch, wie viele Rückruffunktionen in eine einzelne Warteschlange verschoben werden, was völlig falsch ist.

Die Ereignisschleife läuft in einem separaten Thread

Aufgrund des falschen Ereignisschleifendiagramms von node.js dachten einige von uns, Sie hätten zwei Threads. Einer führt Javascript aus und der andere führt die Ereignisschleife aus. Tatsächlich laufen sie alle in einem Thread.

Es gibt eine asynchrone Beteiligung des Betriebssystems an setTimeout

Ein weiteres sehr großes Missverständnis besteht darin, dass die Rückruffunktion von setTimeout aufgerufen wird, nachdem die angegebene Verzögerung abgeschlossen ist (vielleicht Betriebssystem oder Kernel). ) rückt eine Warteschlange vor.

setImmediate setzt die Callback-Funktion an die erste Position

Als übliche Ereignisschleifenbeschreibung gibt es nur eine Warteschlange; daher denken einige Entwickler, dass setImmediate den Callback an die erste Stelle setzt Arbeitswarteschlange vorne. Das ist völlig falsch. Die Arbeitswarteschlangen in Javascript sind „First-In-First-Out“.

Die Architektur der Ereignisschleife

Wenn wir beginnen, den Workflow von zu beschreiben Es ist sehr wichtig, die Architektur der Ereignisschleife zu kennen. Die folgende Abbildung zeigt den tatsächlichen Arbeitsablauf der Ereignisschleife:

Detaillierte Erläuterung des Node.js-Ereignisschleifen-Workflows und -Lebenszyklus

Die verschiedenen Kästchen in der Abbildung stellen unterschiedliche Phasen dar, und jede Phase führt bestimmte Arbeiten aus. Jede Stufe verfügt über eine Warteschlange (hier wird sie hauptsächlich zum besseren Verständnis als Warteschlange bezeichnet; die tatsächliche Datenstruktur ist möglicherweise keine Warteschlange) und Javascript kann in jeder Stufe ausgeführt werden (außer Leerlauf und Vorbereitung). Sie können auch nextTickQueue und microTaskQueue im Bild sehen. Sie sind nicht Teil der Schleife und die darin enthaltenen Rückrufe können jederzeit ausgeführt werden. Ihre Ausführung hat eine höhere Priorität.

Jetzt wissen Sie, dass die Ereignisschleife eine Kombination aus verschiedenen Phasen und verschiedenen Warteschlangen ist. Nachfolgend finden Sie eine Beschreibung jeder Phase.

Timer-Phase

Dies ist die Phase am Anfang der Ereignisschleife. Die an diese Phase gebundene Warteschlange behält jedoch den Timer (setTimeout, setInterval) bei Es schiebt den Rückruf nicht in die Warteschlange, sondern verwendet einen minimalen Heap, um den Timer aufrechtzuerhalten und den Rückruf auszuführen, nachdem das angegebene Ereignis erreicht ist.

Ausstehende (ausstehende) E/A-Callback-Phase

Diese Phase führt die Callbacks in der Pending_Queue in der Ereignisschleife aus. Wenn Sie beispielsweise versuchen, etwas in TCP zu schreiben, wird der Job abgeschlossen und der Rückruf in die Warteschlange verschoben. Hier finden Sie auch die Rückrufe zur Fehlerbehandlung.

Leerlauf, Vorbereitungsphase

Auch wenn der Name inaktiv ist, wird er bei jedem Tick ausgeführt. Bereiten Sie auch Läufe vor, bevor die Polling-Phase beginnt. Wie auch immer, diese beiden Phasen sind die Phasen, in denen der Knoten hauptsächlich einige interne Vorgänge ausführt.

Umfragephase

Die vielleicht wichtigste Phase der gesamten Ereignisschleife ist die Umfragephase. In dieser Phase werden neue eingehende Verbindungen (neuer Socket-Aufbau usw.) und Daten (Lesen von Dateien usw.) akzeptiert. Wir können die Umfragephase in mehrere verschiedene Teile unterteilen.

  1. Wenn sich Dinge in der watch_queue (der Warteschlange, die an die Abfragephase gebunden ist) befinden, werden sie nacheinander ausgeführt, bis die Warteschlange leer ist oder das System das maximale Limit erreicht .

  2. Sobald die Warteschlange leer ist, wartet der Knoten auf neue Verbindungen. Das Warte- oder Schlafereignis hängt von verschiedenen Faktoren ab.

Prüfphase

Die nächste Phase der Abfrage ist die Prüfphase, die setImmediate gewidmet ist. Warum benötigen Sie eine dedizierte Warteschlange, um setImmediate-Rückrufe zu verarbeiten? Dies ist auf das Verhalten der Abfragephase zurückzuführen, das später im Prozessabschnitt erläutert wird. Denken Sie vorerst daran, dass die Prüfphase hauptsächlich den setImmediate()-Rückruf verarbeitet.

Rückruf schließen

Das Schließen des Rückrufs (stocket.on('close', () => {})) wird hier vollständig behandelt, eher so eine Aufräumphase.

nextTickQueue & microTaskQueue

Aufgaben in nextTickQueue bleiben in durch process.nextTick() ausgelösten Rückrufen erhalten. microTaskQueue behält durch Promise ausgelöste Rückrufe bei. Keiner von ihnen ist Teil der Ereignisschleife (nicht in libUV entwickelt), sondern im Knoten. Wenn sich C/C++ und Javascript überschneiden, werden sie so schnell wie möglich aufgerufen. Daher sollten sie ausgeführt werden, nachdem die aktuelle Operation ausgeführt wurde (nicht unbedingt nachdem der aktuelle js-Rückruf ausgeführt wurde).

Ereignisschleifen-Workflow

Wenn Sie node my-script.js in Ihrer Konsole ausführen, richtet Node die Ereignisschleife ein und führt dann Ihr Hauptmodul (my-script) aus. js) Außerhalb der Ereignisschleife. Sobald das Hauptmodul ausgeführt wurde, prüft der Knoten, ob die Schleife noch aktiv ist (gibt es in der Ereignisschleife noch etwas zu tun)? Wenn nicht, wird es beendet, nachdem der Exit-Callback ausgeführt wurde. Prozess, on('exit', foo) Rückruf (Exit-Rückruf). Wenn die Schleife jedoch noch aktiv ist, tritt der Knoten ab der Timer-Phase in die Schleife ein.

Detaillierte Erläuterung des Node.js-Ereignisschleifen-Workflows und -Lebenszyklus

Workflow der Timer-Phase

Die Ereignisschleife tritt in die Timer-Phase ein und prüft, ob etwas vorhanden ist in der Timer-Warteschlange, die ausgeführt werden muss. Okay, das hört sich sehr einfach an, aber die Ereignisschleife muss tatsächlich ein paar Schritte ausführen, um den passenden Rückruf zu finden. Tatsächlich werden Timer-Skripte in aufsteigender Reihenfolge im Heap-Speicher gespeichert. Es erhält zunächst einen Ausführungszeitgeber und berechnet, ob now-registeredTime == delta? Wenn ja, wird der Rückruf des Timers ausgeführt und der nächste Timer überprüft. Bis ein Timer gefunden wird, der noch nicht geplant wurde, wird die Überprüfung anderer Timer gestoppt (da die Timer in aufsteigender Reihenfolge angeordnet sind) und mit der nächsten Stufe fortgefahren.
Angenommen, Sie rufen setTimeout viermal auf, um vier Timer mit Unterschieden von 100, 200, 300 und 400 relativ zur Zeit t zu erstellen.

Detaillierte Erläuterung des Node.js-Ereignisschleifen-Workflows und -Lebenszyklus

Angenommen, die Ereignisschleife tritt bei t+250 in die Timerphase ein. Zunächst wird Timer A betrachtet, dessen Ablaufzeit t+100 beträgt. Aber jetzt ist die Zeit t+250. Daher wird der an Timer A gebundene Rückruf ausgeführt. Überprüfen Sie dann Timer B und stellen Sie fest, dass seine Ablaufzeit t+200 beträgt, sodass auch der Rückruf von B ausgeführt wird. Jetzt wird C überprüft und festgestellt, dass seine Ablaufzeit t+300 beträgt, also wird es verlassen. Die Zeitschleife prüft D nicht, da der Timer in aufsteigender Reihenfolge eingestellt ist. Daher hat D einen größeren Schwellenwert als C. Diese Phase verfügt jedoch über ein systemabhängiges hartes Limit. Wenn die maximale Anzahl von Systemabhängigkeiten erreicht ist, wird mit der nächsten Phase fortgefahren, auch wenn noch nicht ausgeführte Timer vorhanden sind.

Arbeitsablauf der E/A-Phase der ausstehenden Phase

Nach der Timer-Phase tritt die Ereignisschleife in die E/A-Phase der ausstehenden Phase ein und prüft dann, ob Rückrufe vorliegen von vorherigen ausstehenden Aufgaben in der pending_queue. Wenn ja, führen Sie sie nacheinander aus, bis die Warteschlange leer ist oder das maximale Limit des Systems erreicht ist. Danach geht die Ereignisschleife in die Leerlauf-Handler-Phase über, gefolgt von der Vorbereitungsphase für die Durchführung einiger interner Vorgänge. Dann könnte es endlich zur wichtigsten Phase kommen, der Umfragephase.

Umfragephasen-Workflow

Wie der Name schon sagt, handelt es sich hierbei um eine Beobachtungsphase. Beobachten Sie, ob neue Anfragen oder Verbindungen eingehen. Wenn die Ereignisschleife in die Abfragephase eintritt, führt sie Skripte in watcher_queue aus, einschließlich Dateileseantworten, neuen Socket- oder HTTP-Verbindungsanforderungen, bis die Ereignisse erschöpft sind oder die Systemabhängigkeitsgrenze wie in anderen Phasen erreicht wird. Vorausgesetzt, dass keine Rückrufe ausgeführt werden müssen, wartet die Umfrage unter bestimmten Bedingungen eine Weile. Wenn Aufgaben in der Prüfwarteschlange, der Warteschlange für ausstehende Aufgaben, der Warteschlange für schließende Rückrufe oder der Warteschlange für inaktive Handler warten, wird 0 Millisekunden gewartet. Anschließend wird die Wartezeit für die Ausführung des ersten Timers (falls verfügbar) basierend auf dem Timer-Heap bestimmt. Wenn der erste Timer-Schwellenwert überschritten wird, muss nicht gewartet werden (der erste Timer wird ausgeführt).

Workflow der Prüfphase

Nachdem die Umfragephase beendet ist, kommt sofort die Prüfphase. Zu diesem Zeitpunkt befinden sich in der Warteschlange Rückrufe, die durch API SetImmediate ausgelöst werden. Es wird wie andere Phasen nacheinander ausgeführt, bis die Warteschlange leer ist oder die maximale Grenze des abhängigen Systems erreicht ist.

Workflow des Close-Callbacks

Nach Abschluss der Aufgaben in der Prüfphase besteht das nächste Ziel der Ereignisschleife darin, den Close-Callback des Close- oder Destruction-Typs zu verarbeiten. Nachdem die Ereignisschleife die Ausführung der Rückrufe in der Warteschlange zu diesem Zeitpunkt abgeschlossen hat, prüft sie, ob die Schleife noch aktiv ist, und wird andernfalls beendet. Wenn aber noch Arbeit zu erledigen ist, geht es mit der nächsten Schleife weiter; daher die Timer-Phase. Wenn Sie davon ausgehen, dass die Timer (A und B) im vorherigen Beispiel abgelaufen sind, beginnt die Timer-Phase nun bei Timer C, um zu prüfen, ob sie abgelaufen sind.

nextTickQueue & microTaskQueue

Wann werden also die Rückruffunktionen dieser beiden Warteschlangen ausgeführt? Sie laufen natürlich so schnell wie möglich, bevor sie von der aktuellen Stufe zur nächsten Stufe übergehen. Im Gegensatz zu anderen Phasen gibt es für sie keine systemabhängigen Kater-Einschränkungen und der Knoten führt sie aus, bis beide Warteschlangen leer sind. Allerdings hat nextTickQueue eine höhere Aufgabenpriorität als microTaskQueue.

Thread-Pool

Ein häufiges Wort, das ich von Javascript-Entwicklern gehört habe, ist ThreadPool. Ein häufiges Missverständnis ist, dass NodeJS über einen Prozesspool verfügt, der alle asynchronen Vorgänge abwickelt. Tatsächlich befindet sich der Prozesspool jedoch in der Bibliothek libUV (einer Drittanbieterbibliothek, die von NodeJS zur Verarbeitung asynchroner Prozesse verwendet wird). Der Grund dafür, dass es im Diagramm nicht angezeigt wird, liegt darin, dass es nicht Teil des Zyklusmechanismus ist. Derzeit wird nicht jede asynchrone Aufgabe vom Prozesspool verarbeitet. libUV nutzt flexibel die asynchronen APIs des Betriebssystems, um die Umgebung ereignisgesteuert zu halten. Die API des Betriebssystems kann jedoch keine Dateien lesen, DNS-Abfragen usw. durchführen. Diese werden vom Prozesspool verarbeitet, der standardmäßig nur 4 Prozesse umfasst. Sie können die Anzahl der Prozesse erhöhen, indem Sie die Umgebungsvariable von uv_threadpool_size auf 128 setzen.

Workflow mit Beispielen

Ich hoffe, Sie verstehen, wie die Ereignisschleife funktioniert. Durch die Synchronisierung in der C-Sprache wird Javascript asynchron. Es verarbeitet jeweils nur eine Sache, ist aber sehr blockierend. Natürlich lässt sich die Theorie überall dort, wo wir sie beschreiben, am besten anhand von Beispielen verstehen. Lassen Sie uns dieses Skript also anhand einiger Codeausschnitte verstehen.

Fragment 1 – Grundlegendes Verständnis
setTimeout(() => {console.log('setTimeout'); }, 0); 
setImmediate(() => {console.log('setImmediate'); });
Nach dem Login kopieren

Können Sie die obige Ausgabe erraten? Nun, man könnte meinen, dass setTimeout zuerst gedruckt wird, aber es gibt keine Garantie, warum? Nach der Ausführung des Hauptmoduls und dem Eintritt in die Timer-Phase stellt er möglicherweise nicht fest oder stellt fest, dass Ihr Timer abgelaufen ist. Warum? Ein Timer-Skript wird basierend auf der Systemzeit und der von Ihnen angegebenen Delta-Zeit registriert. Gleichzeitig mit dem Aufruf von setTimeout wird das Timer-Skript in den Speicher geschrieben. Abhängig von der Leistung Ihres Computers und anderer darauf ausgeführter Vorgänge (keine Knoten) kann es zu einer kleinen Verzögerung kommen. An einem anderen Punkt setzt der Knoten nur eine Variable „Jetzt“, bevor er in die Timer-Phase eintritt (jede Durchlaufrunde), und verwendet „Jetzt“ als aktuelle Zeit. Man könnte also sagen, dass mit dem Äquivalent der genauen Zeit etwas nicht stimmt. Dies ist der Grund für die Unsicherheit. Wenn Sie in einem Rückruf eines Timer-Codes auf denselben Code verweisen, erhalten Sie das gleiche Ergebnis.

Wenn Sie diesen Code jedoch in den E/A-Zyklus verschieben, ist garantiert, dass der setImmediate-Rückruf vor setTimeout ausgeführt wird.

fs.readFile('my-file-path.txt', () => {
    setTimeout(() => {console.log('setTimeout');}, 0);               
    setImmediate(() => {console.log('setImmediate');}); });
Nach dem Login kopieren
Clip 2 – Timer besser verstehen
var i = 0;
var start = new Date();
function foo () {
    i++;
    if (i <p>Das obige Beispiel ist sehr einfach. Rufen Sie die Funktion foo auf und rufen Sie dann foo rekursiv über setImmediate bis 1000 auf. Auf meinem Computer dauerte es etwa 6 bis 8 Millisekunden. Fee, bitte ändere den obigen Code und ersetze setImmedaite(foo) durch setTimeout(foo, o). </p><pre class="brush:php;toolbar:false">var i = 0;
var start = new Date();
function foo () {
    i++;
    if (i <p>Im Moment dauert die Ausführung dieses Codes auf meinem Computer mehr als 1400 ms. Warum passiert das? Keiner von ihnen hat E/A-Ereignisse, sie sollten gleich sein. Das Warteereignis für die beiden oben genannten Beispiele ist 0. Warum dauert es so lange? Durch den Ereignisvergleich wurden Abweichungen festgestellt, wobei CPU-intensive Aufgaben mehr Zeit in Anspruch nahmen. Registrierte Timer-Skripte konsumieren auch Ereignisse. Für jede Phase eines Timers müssen einige Vorgänge ausgeführt werden, um zu bestimmen, ob ein Timer ausgeführt werden soll. Eine längere Ausführung führt auch zu mehr Ticks. In setImmediate gibt es jedoch nur eine Phase der Überprüfung, als ob sie in einer Warteschlange stünde und dann ausgeführt würde. </p><h5><strong>Fragment 3 – Grundlegendes zur Ausführung von nextTick() und Timer (Timer)</strong></h5><pre class="brush:php;toolbar:false">var i = 0;
function foo(){
    i++;
    if (i>20) return;
    console.log("foo");
    setTimeout(()=>console.log("setTimeout"), 0);       
    process.nextTick(foo);
}
setTimeout(foo, 2000);
Nach dem Login kopieren

Was ist Ihrer Meinung nach die obige Ausgabe? Ja, es wird foo und dann setTimeout ausgegeben. Nach 2 Sekunden wird foo() rekursiv von nextTickQueue aufgerufen, um das erste foo auszudrucken. Wenn alle nextTickQueue ausgeführt wurden, beginnt die Ausführung anderer (z. B. setTimeout-Rückrufe).

Nachdem jeder Rückruf ausgeführt wurde, wird nextTickQueue überprüft? Lassen Sie uns den Code ändern und einen Blick darauf werfen.

var i = 0;
function foo(){
    i++;
    if (i>20) return;
    console.log("foo");
    setTimeout(()=>console.log("setTimeout"), 0);       
    process.nextTick(foo);
}
setTimeout(foo, 2000);
setTimeout(()=>{console.log("Other setTimeout"); }, 2000);
Nach dem Login kopieren

Nach setTimeout habe ich gerade ein weiteres setTimeout hinzugefügt, das „Other setTimeout“ mit derselben Verzögerungszeit ausgibt. Obwohl dies nicht garantiert ist, ist es möglich, dass „Other setTimeout“ ausgegeben wird, nachdem das erste foo ausgegeben wurde. Derselbe Timer wird in einer Gruppe gruppiert und nextTickQueue wird ausgeführt, nachdem die laufende Rückrufgruppe ausgeführt wurde.

Einige häufige Fragen

Wo wird der Javascript-Code ausgeführt?

Da die meisten von uns die Ereignisschleife als einen separaten Thread betrachten, werden Rückrufe in eine Warteschlange verschoben und nacheinander ausgeführt. Leser, die diesen Artikel zum ersten Mal lesen, fragen sich möglicherweise: Wo wird Javascript ausgeführt? Wie ich bereits sagte, gibt es nur einen Thread, und hier wird auch Javascript-Code aus der Ereignisschleife selbst mit V8 oder anderen Engines ausgeführt. Die Ausführung erfolgt synchron und die Ereignisschleife wird nicht weitergegeben, wenn die aktuelle Javascript-Ausführung noch nicht abgeschlossen ist.

Wir haben setTimeout(fn, 0), warum brauchen wir setImmediate?

Erstens ist es nicht 0, sondern 1. Wenn Sie einen Timer mit einer Zeit kleiner als 1 oder größer als 2147483647 ms einstellen, wird er automatisch auf 1 gesetzt. Wenn Sie also Wenn Sie die Verzögerungszeit von setTimeout auf 0 setzen, wird sie automatisch auf 1 gesetzt.

Darüber hinaus reduziert setImmediate zusätzliche Prüfungen. Daher wird setImmediate schneller ausgeführt. Es wird auch nach der Polling-Phase platziert, sodass der setImmediate-Rückruf von jeder eingehenden Anfrage sofort ausgeführt wird.

Warum wird setImmediate von Comprehension aufgerufen?

setImmediate und process.nextTick() sind beide falsch benannt. Funktionell wird setImmediate also beim nächsten Tick ausgeführt, und nextTick wird sofort ausgeführt.

Wird Javascript-Code blockiert?

Weil nextTickQueue keine Begrenzung für die Rückrufausführung hat. Wenn Sie also „process.nextTick()“ rekursiv ausführen, kommt Ihr Programm möglicherweise nie aus der Ereignisschleife heraus, unabhängig davon, was Sie in anderen Phasen haben.

Was passiert, wenn ich setTimeout während der Exit-Callback-Phase aufrufe?

Der Timer wird möglicherweise initialisiert, der Rückruf wird jedoch möglicherweise nie aufgerufen. Denn wenn sich der Knoten in der Exit-Callback-Phase befindet, ist er bereits aus der Ereignisschleife gesprungen. Es gibt also kein Zurück zur Ausführung.

Einige kurze Schlussfolgerungen

Die Ereignisschleife hat keinen Arbeitsstapel

Die Ereignisschleife befindet sich nicht in einem separaten Thread und die Ausführung von Javascript ist nicht wie aus der Warteschlange. Es ist so einfach, einen Rückruf zur Ausführung aufzurufen.

setImmediate verschiebt den Rückruf nicht an den Kopf der Arbeitswarteschlange, es gibt eine eigene Phase und Warteschlange.

setImmediate wird in der nächsten Schleife ausgeführt, und nextTick wird tatsächlich sofort ausgeführt.

Seien Sie vorsichtig, nextTickQueue kann Ihren Knotencode blockieren, wenn es rekursiv aufgerufen wird.

Verwandte Empfehlungen:

Eingehende Analyse des Node.js-Ereignisses loop_node.js

Einführung in den Lebenszyklus von JS-Steuerelementen_javascript Tipps

Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung des Node.js-Ereignisschleifen-Workflows und -Lebenszyklus. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!