Interviewfragen im Zusammenhang mit der Polling-Warteschlange (Untersuchung der Ausführungsreihenfolge von Timern, Polling und Check)
weiterTick and Promise
Fragen im Vorstellungsgespräch
Mindmap
Knoten-Ereignisschleife
Die zugrunde liegende Sprache von Node ist libuv , eine C++-Sprache. Es dient zum Betrieb des zugrundeliegenden Betriebssystems und kapselt die Betriebssystemschnittstelle. Die Ereignisschleife von Node ist ebenfalls in libuv geschrieben, daher unterscheidet sich der Lebenszyklus von Node von dem des Browsers.
Da Node mit dem Betriebssystem arbeitet, ist die Ereignisschleife relativ komplex und verfügt über einige eigene einzigartige APIs. Die Ereignisschleife weist in verschiedenen Betriebssystemen einige geringfügige Unterschiede auf. Hierzu sind Kenntnisse im Bereich Betriebssysteme erforderlich, auf die hier jedoch nicht näher eingegangen wird. Dieses Mal stellen wir nur den Betriebsablauf von Node im JS-Hauptthread vor. Andere Node-Threads werden vorerst nicht erweitert.
Ereignisschleifendiagramm
Wie versprochen gibt es ein Bild und ich will euch nicht auf die Folter spannen. Wenn Sie das folgende Bild verstehen, haben Sie die Ereignisschleife gelernt.
Ereignisschleifendiagramm
Ereignisschleifendiagramm - Struktur
Um jedem einen allgemeinen Überblick zu geben, hier ein Verzeichnisstrukturdiagramm :
Inhaltsverzeichnis
Lassen Sie uns als Nächstes ausführlich darüber sprechen.
Hauptthread
Hauptthread
Im obigen Bild haben die einzelnen Farbblöcke folgende Bedeutung:
main : Starten Sie die Eingabedatei und führen Sie die Hauptfunktion aus
event loop : Überprüfen Sie, ob die Ereignisschleife betreten werden soll. Überprüfen Sie, ob in anderen Threads ausstehende Aufgaben vorhanden sind. Überprüfen Sie, ob noch andere Aufgaben ausgeführt werden (z. B. Timer, Dateilesevorgänge usw.). Wenn die obige Situation eintritt, treten Sie in die Ereignisschleife ein und führen Sie andere Aufgaben aus
Der Ablauf der Ereignisschleife: Verfolgen Sie den Ablauf von Timern bis hin zum Schließen von Rückrufen. Gehen Sie zur Ereignisschleife, um zu sehen, ob sie beendet ist. Wenn nicht, gehen Sie einen weiteren Kreis durch.
over : alles ist vorbei, beendet
Ereignisschleife
Ereignisschleife
Der graue Kreis in der Abbildung bezieht sich auf das Betriebssystem und ist nicht Schwerpunkt der Analyse dieses Kapitels. Achten Sie besonders auf die gelben und orangefarbenen Kreise und das orangefarbene Kästchen in der Mitte.
Wir nennen jede Runde der Ereignisschleife „einen Zyklus“, auch „Umfrage“ oder „Tick“ genannt.
Ein Zyklus durchläuft sechs Phasen:
Timer: Timer (in ihnen sind Callback-Funktionen von setTimeout, setInterval usw. gespeichert)
ausstehender Rückruf
Leerlauf vorbereiten
poll: Polling-Warteschlange (hier werden andere Rückrufe als Timer und Prüfungen gespeichert)
check: Prüfphase (Rückrufe mit „setImmediate“ gelangen direkt in diese Warteschlange )
Rückrufe schließen
Dieses Mal konzentrieren wir uns nur auf die drei oben rot markierten Schlüsselpunkte.
So funktioniert es
Jede Phase verwaltet eine Ereigniswarteschlange. Sie können sich jeden Kreis als eine Ereigniswarteschlange vorstellen.
Dies unterscheidet sich von Browsern, die höchstens zwei Warteschlangen haben (Makro-Warteschlange und Mikro-Warteschlange). Aber es gibt sechs Warteschlangen im Knoten
Überprüfen Sie nach dem Erreichen einer Warteschlange, ob sich in der Warteschlange eine Aufgabe befindet (d. h., ob eine Rückruffunktion vorhanden ist), die ausgeführt werden muss. Wenn dies der Fall ist, werden sie der Reihe nach ausgeführt, bis alle ausgeführt sind und die Warteschlange geleert ist.
Wenn keine Aufgabe vorhanden ist, gehen Sie zur Überprüfung zur nächsten Warteschlange. Bis alle Warteschlangen überprüft wurden, wird dies als eine Umfrage betrachtet.
Darunter gelangen timers , pending callback , idle prepare usw. nach der Ausführung in poll Warteschlange.
So funktioniert die Timer-Warteschlange
Timer sind keine Warteschlangen im eigentlichen Sinne, sie speichern Timer in sich. Jedes Mal, wenn diese Warteschlange erreicht wird, werden alle Timer im Timer-Thread überprüft und mehrere Timer im Timer-Thread werden in chronologischer Reihenfolge sortiert.
Prüfvorgang: Berechnen Sie der Reihe nach jeden Timer und berechnen Sie, ob die Zeit vom Start des Timers bis zur aktuellen Zeit der Einstellung des Timerintervallparameters entspricht (z. B. 1000 ms, berechnen Sie, ob seit dem Start des Timers 1 Minute vergangen ist). Wenn eine Timerprüfung erfolgreich ist, wird ihre Rückruffunktion ausgeführt.
So funktioniert die Poll-Warteschlange
Wenn in der Umfrage Rückruffunktionen vorhanden sind, die ausgeführt werden müssen, werden die Rückrufe der Reihe nach ausgeführt, bis die Warteschlange geleert ist.
Wenn in der Umfrage keine Rückruffunktion vorhanden ist, die ausgeführt werden muss, ist die Warteschlange leer. Hier wird gewartet, bis Rückrufe in anderen Warteschlangen erscheinen.
Wenn in anderen Warteschlangen ein Rückruf erfolgt, wird der Prozess von der Abfrage zum Ende weitergeleitet, wodurch diese Phase beendet und die nächste Phase eingeleitet wird.
Wenn in anderen Warteschlangen kein Rückruf vorliegt, wird weiterhin in der Abfragewarteschlange gewartet, bis in einer beliebigen Warteschlange ein Rückruf vorliegt, und dann gearbeitet. (So macht ein fauler Idiot die Dinge)
Betreten Sie den Hauptthread und führen Sie setTimeout() aus. Die Callback-Funktion wird als asynchrone Task in die asynchrone Warteschlange der Timer gestellt und vorerst nicht ausgeführt.
Fahren Sie fort und führen Sie die Konsole nach dem Timer aus, um „Knoten“ auszudrucken.
Stellen Sie fest, ob eine Ereignisschleife vorhanden ist. Ja, führen Sie die Abfrage durch: von Timern – ausstehender Rückruf – Leerlaufvorbereitung …
Gehen Sie zur Polling-Warteschlange, um die Schleife zu stoppen und zu warten.
Da noch keine 5 Sekunden vergangen sind, befindet sich keine Aufgabe in der Warteschlange des Timers. Daher bleibt er in der Abfragewarteschlange hängen und fragt andere Warteschlangen ab, um zu prüfen, ob Aufgaben vorhanden sind.
Nach 5 Sekunden wird der Rückruf von setTimeout in die Timer eingefügt. Die Routine prüft durch Abfragen, ob sich eine Aufgabe in der Timerwarteschlange befindet, sodass sie nach unten geht und die Timer nach der Prüfung und dem Schließen der Rückrufe erreicht. Löschen Sie die Timer-Warteschlange.
Fahren Sie mit der Abfrage fort und warten Sie, um zu fragen, ob die Ereignisschleife noch benötigt wird. Wenn nicht, wird sie fortgesetzt und beendet.
Um dieses Problem zu verstehen, sehen Sie sich den folgenden Code und die Prozessanalyse an:
Führen Sie wie üblich zuerst den Hauptthread aus, drucken Sie den „Knotenlebenszyklus“, führen Sie http ein und erstellen Sie den http-Dienst.
Anschließend prüft die Ereignisschleife, ob asynchrone Tasks vorhanden sind und findet Timer-Tasks und Anforderungs-Tasks. Geben Sie also die Ereignisschleife ein.
Wenn sich in den sechs Warteschlangen keine Aufgaben befinden, warten sie in der Polling-Warteschlange. Wie unten dargestellt:
Nach fünf Sekunden befindet sich eine Aufgabe in den Timern, und der Prozess wird aus der Abfrage freigegeben, durchläuft die Warteschlangen für die Prüf- und Schließ-Rückrufe und erreicht die Ereignisschleife.
Die Ereignisschleife prüft, ob asynchrone Aufgaben vorhanden sind und findet Timeraufgaben und Anforderungsaufgaben. Geben Sie also die Ereignisschleife erneut ein.
Wenn es die Timer-Warteschlange erreicht und eine Callback-Funktionsaufgabe findet, führt es die Callbacks der Reihe nach aus, löscht die Timer-Warteschlange (natürlich gibt es nur einen Callback nach 5 Sekunden, sodass dieser direkt ausgeführt werden kann) und druckt „setTimeout“ aus. Wie unten gezeigt
Nach dem Löschen der Timer-Warteschlange wird die Abfrage bis zur Abfragewarteschlange fortgesetzt. Da die Abfragewarteschlange nun leer ist, wartet sie hier.
Später wird, vorausgesetzt dass eine Benutzeranforderung eingeht, die Rückruffunktion h1 in die Abfragewarteschlange gestellt. Daher gibt es in der Umfrage Rückruffunktionen, die ausgeführt werden müssen, und die Rückrufe werden nacheinander ausgeführt, bis die Umfragewarteschlange geleert ist.
Die Polling-Warteschlange wird gelöscht. Zu diesem Zeitpunkt ist die Polling-Warteschlange leer und wartet weiter.
Da sich der Knoten-Thread immer in der Polling-Warteschlange befindet, wird er, wenn über einen längeren Zeitraum keine Aufgabe vorhanden ist, automatisch getrennt und wartet (nicht vertrauenswürdige Leistung), und führt den Polling-Prozess nach unten aus. Nach dem Überprüfen und Schließen der Rückrufe erreicht er die Ereignisschleife.
Überprüfen Sie nach Erreichen der Ereignisschleife, ob eine asynchrone Aufgabe vorhanden ist, und stellen Sie fest, dass eine Anforderungsaufgabe vorhanden ist. (Zu diesem Zeitpunkt wurde die Timer-Aufgabe ausgeführt und existiert daher nicht mehr.) Fahren Sie dann fort, um erneut in die Ereignisschleife einzutreten.
Ankunft an der Warteschlange zur Wahlurne, wieder warten …
Wenn nach einer langen Wartezeit keine Aufgabe kommt, wird die Verbindung zur Even-Schleife automatisch getrennt (fügen Sie etwas mehr zur Schleifensituation ohne Aufgabe hinzu).
Kehren Sie erneut zur Polling-Warteschlange zurück und unterbrechen Sie
Endlosschleife...
Sortieren des Flussdiagramms der Ereignisschleife:
Hinweis: Der Begriff „Gibt es eine Aufgabe“ in der folgenden Abbildung bedeutet „Gibt es eine Aufgabe in dieser Warteschlange?“
Zusammenfassung des Ereignisschleifenprozesses
Lassen Sie uns den Vorgang anhand eines typischen Beispiels überprüfen:
Führen Sie den Vorgang dreimal hintereinander aus und drucken Sie die folgenden Ergebnisse aus:
Analyse des Ausführungsprozesses:
Führen Sie den globalen Kontext aus und drucken Sie „Knotenlebenszyklus + Zeit“
Fragen Sie, ob eine Ereignisschleife vorhanden ist
Ja, rufen Sie die Timer-Warteschlange auf und prüfen Sie, ob ein Timer vorhanden ist (die CPU-Verarbeitungsgeschwindigkeit ist in Ordnung und hat zu diesem Zeitpunkt noch nicht 200 ms erreicht).
Beim Polling wird die Polling-Warteschlange aufgerufen, aber die Datei wurde noch nicht vollständig gelesen (z. B. hat es zu diesem Zeitpunkt nur 20 ms gedauert), sodass die Polling-Warteschlange leer ist und kein Task-Rückruf erfolgt.
Warten in der Polling-Warteschlange ... Polling weiter, um zu sehen, ob ein Rückruf erfolgt
Nachdem die Datei gelesen wurde, verfügt die Polling-Warteschlange über die Rückruffunktion fsFunc, die ausgeführt wird und „fs + time“ ausgibt.
Die While-Schleife bleibt 300 Millisekunden lang hängen.
Wenn die Endlosschleife 200 ms erreicht, wird der F1-Rückruf in die Timer-Warteschlange eingereiht. Zu diesem Zeitpunkt ist die Polling-Warteschlange jedoch sehr ausgelastet und belegt den Thread, sodass keine weitere Ausführung erfolgt.
Nach 300 ms wird die Polling-Warteschlange gelöscht und die Ausgabe lautet „Ende der Endlosschleife + Zeit“
Die Ereignisschleife bricht schnell zusammen
Die Zeitnehmer sind wieder an der Reihe und der F1-Rückruf in der Zeitnehmerwarteschlange wird ausgeführt. Also sah ich "setTimeout + time"
Die Timer-Warteschlange wird gelöscht und kehrt zur Polling-Warteschlange zurück. Es ist keine Aufgabe vorhanden, warten Sie also einen Moment.
Nachdem Sie lange genug gewartet haben, gehen Sie zurück zur Ereignisschleife.
Die Ereignisschleife prüft, ob keine anderen asynchronen Aufgaben vorhanden sind, beendet den Thread und das gesamte Programm wird beendet.
Prüfphase
Prüfphase (Rückrufe mit „setImmediate“ gehen direkt in diese Warteschlange)
So funktioniert die Check-Warteschlange tatsächlich
Die eigentliche Warteschlange enthält eine Sammlung auszuführender Rückruffunktionen. Ähnlich der Form [fn,fn]. Jedes Mal, wenn Sie die Prüfwarteschlange erreichen, können Sie die Rückruffunktion sofort nacheinander ausführen [ähnlich wie [fn1,fn2].forEach((fn)=>fn())]
Daher ist setImmediate kein Timer-Konzept.
Wenn Sie zu einem Vorstellungsgespräch gehen und es dabei um Node geht, werden Sie möglicherweise mit der folgenden Frage konfrontiert: Was ist schneller, setImmediate oder setTimeout(0)?
setImmediate() vs. setTimeout(0)
Der Rückruf von setImmediate ist asynchron, was mit der Rückrufnatur von setTimeout übereinstimmt.
Der setImmediate-Rückruf befindet sich in der check und der setTimeout-Rückruf in der timers Warteschlange (konzeptionell gesehen befindet er sich tatsächlich im Timer-Thread, aber setTimeout führt einen Prüfaufruf in der Timer-Warteschlange aus. Weitere Informationen finden Sie unter Funktionsweise von Timern).
Nachdem die Funktion „setImmediate“ aufgerufen wurde, wird die Rückruffunktion sofort in die Prüfwarteschlange gestellt und in der nächsten Ereignisschleife ausgeführt. Nach dem Aufruf der Funktion setTimeout fügt der Timer-Thread eine Timer-Aufgabe hinzu. Beim nächsten Aufruf der Ereignisschleife wird in der Timer-Phase geprüft, ob die Timer-Aufgabe angekommen ist. Wenn ja, wird die Callback-Funktion ausgeführt.
Zusammenfassend lässt sich sagen, dass setImmediate schneller arbeitet als setTimeout(0), da setTimeout auch einen Timer-Thread starten muss und den Rechenaufwand erhöht.
Die Auswirkungen beider sind ähnlich. Doch die Reihenfolge der Hinrichtung ist ungewiss
Nach mehrmaliger Ausführung ist der Ausführungseffekt wie folgt:
Unsichere Reihenfolge
Man sieht, dass die Reihenfolge der beiden console.log-Anweisungen bei mehrmaliger Ausführung nicht festgelegt ist. Dies liegt daran, dass die minimale Intervallzahl von setTimeout 1 ist, obwohl der folgende Code mit 0 aufgefüllt ist. Die tatsächliche Computerausführung wird jedoch mit 1 ms berechnet. (Beachten Sie, dass dies sich vom Browser-Timer unterscheidet. Im Browser beträgt das Mindestintervall von setInterval 10 ms, und wenn es weniger als 10 ms beträgt, wird es auf 10 gesetzt; wenn das Gerät eingeschaltet ist, beträgt das Mindestintervall 16,6 ms.)
Im obigen Code wird, wenn der Hauptthread ausgeführt wird, die Funktion setTimeout aufgerufen und der Timer-Thread fügt eine Timer-Aufgabe hinzu. Nachdem die Funktion „setImmediate“ aufgerufen wurde, wird ihre Rückruffunktion sofort in die Prüfwarteschlange gestellt. Der Hauptthread hat die Ausführung abgeschlossen.
Wenn eventloop feststellt, dass sich Inhalt in den Timern und Prüfwarteschlangen befindet, beginnt es mit der asynchronen Abfrage:
Der erste Fall: Wenn die Zeit in den Timern erreicht ist, ist möglicherweise nicht mehr 1 ms übrig und die Bedingung des Timer-Task-Intervalls ist nicht erfüllt. Daher gibt es in den Timern keine Rückruffunktion. Fahren Sie mit der Überprüfungswarteschlange fort. Zu diesem Zeitpunkt wartet die Rückruffunktion von setImmediate schon lange und wird direkt ausgeführt. Wenn die Ereignisschleife das nächste Mal die Warteschlange des Timers erreicht, ist der Timer abgelaufen und die Callback-Aufgabe „setTimeout“ wird ausgeführt. Die Reihenfolge ist also „setImmediate -> setTimeout“.
Der zweite Fall: Es ist aber auch möglich, dass es 1 ms überschreitet, wenn es die Timerstufe erreicht. Daher ist die Bedingung des Berechnungstimers erfüllt und die Callback-Funktion setTimeout wird direkt ausgeführt. Die Ereignisschleife geht dann zur Prüfwarteschlange, um den Rückruf von setImmediate auszuführen. Die endgültige Reihenfolge ist „setTimeout -> setImmediate“.
Daher hängt das endgültige Ergebnis der Ausführungsreihenfolge der beiden Funktionen beim Vergleich nur dieser beiden Funktionen von der aktuellen Betriebsumgebung und Betriebsgeschwindigkeit des Computers ab.
Vergleichscode der Zeitdifferenz zwischen den beiden
------------------setTimeout-Test:------------------
sei i = 0;
Konsole.Zeit('Zeitlimit festlegen');
Funktion test() {
wenn (i < 1000) {
setzeTimeout(test, 0)
ich++
} anders {
Konsole.timeEnd('Zeitüberschreitung festlegen');
}
}
prüfen();
------------------setImmediate-Test:------------------
sei i = 0;
Konsole.Zeit('setImmediate');
Funktion test() {
wenn (i < 1000) {
setImmediate(test)
ich++
} anders {
Konsole.timeEnd('setImmediate');
}
}
prüfen();
Laufen und den Zeitabstand beachten:
Der Zeitunterschied zwischen setTimeout und setImmediate
Es ist ersichtlich , dass setTimeout viel mehr Zeit benötigt als setImmediate
Dies liegt daran, dass setTimeout nicht nur die Zeit der Hauptcodeausführung verbraucht. Außerdem befindet sich in der Timer-Warteschlange die Berechnungszeit für jede geplante Aufgabe im Timer-Thread.
Interviewfragen im Zusammenhang mit der Polling-Warteschlange (Untersuchung der Ausführungsreihenfolge von Timern, Polling und Check)
Wenn Sie das obige Ereignisschleifendiagramm verstehen, wird Ihnen die folgende Frage nicht schwer fallen!
// Besprechen Sie die Ausführungsreihenfolge des folgenden Codes. Welcher wird zuerst gedruckt?
const fs = erfordern('fs')
fs.readFile('./poll.js', () => {
setTimeout(() => console.log('setTimeout'), 0)
setImmediate(() => console.log('setImmediate'))
})
Unabhängig davon, wie oft die obige Codelogik ausgeführt wird, wird setImmediate immer zuerst ausgeführt.
Führen Sie zuerst „setImmediate“ aus.
Weil die Rückrufe jeder FS-Funktion in die Polling-Warteschlange gestellt werden. Wenn das Programm in der Polling-Warteschlange hängt, wird sofort ein Rückruf ausgeführt. Nachdem die Funktionen setTimeout und setImmediate im Rückruf ausgeführt wurden, wird die Prüfwarteschlange sofort zum Rückruf hinzugefügt. Nachdem der Rückruf ausgeführt wurde, werden andere Warteschlangen abgefragt, um zu prüfen, ob Inhalt vorhanden ist. Anschließend beendet das Programm das Halten der Abfragewarteschlange und führt die Abfrage nach unten aus. Die Prüfung ist die nächste Phase nach der Umfrage. Daher wird im Abwärtsprozess zuerst der Rückruf in der Prüfphase ausgeführt, dh setImmediate wird zuerst gedruckt. Im nächsten Zyklus wird beim Erreichen der Timer-Warteschlange überprüft, ob der SetTimeout-Timer die Bedingungen erfüllt, und der Timer-Rückruf wird ausgeführt.
weiterTick and Promise
Nachdem wir über Makroaufgaben gesprochen haben, sprechen wir nun über Mikroaufgaben.
Bei beiden handelt es sich um „Mikrowarteschlangen“, die asynchrone Mikroaufgaben ausführen.
Die beiden sind nicht Teil der Ereignisschleife und das Programm startet keine zusätzlichen Threads, um verwandte Aufgaben zu verarbeiten. (Verständnis: Wenn eine Netzwerkanforderung in einem Versprechen gesendet wird, handelt es sich um den von der Netzwerkanforderung geöffneten Netzwerkthread, der nichts mit der Mikroaufgabe des Versprechens zu tun hat.)
Der Zweck der Einrichtung einer Mikrowarteschlange besteht darin, einige Aufgaben „sofort“ oder „unverzüglich“ zu priorisieren.
Im Vergleich zu Promise liegt nextTick auf einem höheren Niveau.
NextTick-Ausdruck
Prozess.nextTick(() => {})
Versprechensdarstellung
Versprechen.auflösen().dann(() => {})
Wie nehme ich an der Eventschleife teil?
Löschen Sie in der Ereignisschleife vor der Ausführung jedes Rückrufs nacheinander „nextTick“ und „promise“.
// Betrachten Sie zuerst die Ausführungsreihenfolge des folgenden Codes setImmediate(() => {
Konsole.log('setImmediate');
});
Prozess.nextTick(() => {
console.log('nächsterTick 1');
Prozess.nextTick(() => {
console.log('nächsterTick 2');
})
})
konsole.log('global');
Versprechen.auflösen().dann(() => {
console.log('Versprechen 1');
Prozess.nextTick(() => {
console.log('nextTick im Versprechen');
})
})
Endgültige Reihenfolge:
weltweit
nächsterTick 1
nächsterTick 2
Versprechen 1
nächstesHäkchen im Versprechen
Sofort festlegen
Zwei Fragen:
Vor diesem Hintergrund müssen zwei Fragen berücksichtigt und gelöst werden:
1. Überprüfen Sie nextTick und Promise jedes Mal, wenn eine asynchrone Makro-Task-Warteschlange ausgeführt wird? Oder sollten wir es jedes Mal überprüfen, wenn eine Rückruffunktion in der Makroaufgabenwarteschlange ausgeführt wird?
2. Wenn während der Haltephase einer Umfrage ein nextTick- oder Promise-Rückruf eingefügt wird, wird das Halten der Umfragewarteschlange dann sofort gestoppt, um den Rückruf auszuführen?
Informationen zu den beiden obigen Fragen finden Sie im folgenden Code.
setzeTimeout(() => {
console.log('setTimeout 100');
setzeTimeout(() => {
console.log('setTimeout 100 - 0');
Prozess.nextTick(() => {
console.log('nextTick in setTimeout 100 - 0');
})
}, 0)
setImmediate(() => {
console.log('setImmediate in setTimeout 100');
Prozess.nextTick(() => {
console.log('nextTick in setImmediate in setTimeout 100');
})
});
Prozess.nextTick(() => {
console.log('nextTick in setTimeout100');
})
Versprechen.auflösen().dann(() => {
console.log('Versprechen in setTimeout100');
})
}, 100)
const fs = erfordern('fs')
fs.readFile('./1.poll.js', () => {
console.log('Umfrage 1');
Prozess.nextTick(() => {
console.log('nextTick in Umfrage ======');
})
})
setzeTimeout(() => {
console.log('setTimeout 0');
Prozess.nextTick(() => {
console.log('nextTick in setTimeout');
})
}, 0)
setzeTimeout(() => {
console.log('setTimeout 1');
Versprechen.auflösen().dann(() => {
console.log('Versprechen in setTimeout1');
})
Prozess.nextTick(() => {
console.log('nextTick in setTimeout1');
})
}, 1)
setImmediate(() => {
Konsole.log('setImmediate');
Prozess.nextTick(() => {
console.log('nextTick in setImmediate');
})
});
Prozess.nextTick(() => {
console.log('nächsterTick 1');
Prozess.nextTick(() => {
console.log('nextTick 2');
})
})
console.log('global ------');
Versprechen.auflösen().dann(() => {
console.log('Versprechen 1');
Prozess.nextTick(() => {
console.log('nextTick im Versprechen');
})
})
/** Die Ausführungsreihenfolge ist global wie folgt ------
nächsterTick 1
nächsterTick 2
Versprechen 1
nächstesHäkchen im Versprechen
setTimeout 0 // Erklärung des Problems 1. Ohne nextTick und Promise oben ist die Reihenfolge von setTimeout und setImmediate nicht sicher. Mit ihnen wird 0 definitiv zuerst starten.
// Es ist ersichtlich, dass vor der Ausführung einer Warteschlange zuerst die Mikrowarteschlangen nextTick und promise geprüft und ausgeführt werden. nextTick in setTimeout
setTimeout 1
nächsterTick in setTimeout1
Versprechen in setTimeout1
Sofort festlegen
nächstes Häkchen in setImmediate
Umfrage 1
nextTick in Umfrage ======
setTimeout 100
nächsterTick in setTimeout100
Versprechen in setTimeout100
setImmediate in setTimeout 100
nextTick in setImmediate in setTimeout 100
setTimeout 100 - 0
nächsterTick in setTimeout 100 - 0
*/
Der obige Code wird mehrmals in derselben Reihenfolge ausgeführt und die Reihenfolge von setTimeout und setImmediate bleibt unverändert.
Die Reihenfolge der Ausführung und die genauen Gründe sind wie folgt:
global : Hauptthread-Synchronisierungsaufgabe, erste Ausführung ist OK
nextTick 1 : Vor der Ausführung der asynchronen Makroaufgabe muss die asynchrone Mikroaufgabe gelöscht werden. nextTick hat eine hohe Priorität und wird einen Schritt früher ausgeführt.
nextTick 2 : Nach der Ausführung des obigen Codes wird sofort ein weiterer nextTick-Mikrotask ausgeführt
promise 1 : Löschen Sie die asynchrone Mikroaufgabe, bevor Sie die asynchrone Makroaufgabe ausführen. Das Versprechen hat eine niedrige Priorität und wird daher unmittelbar nach Abschluss von nextTick ausgeführt.
nextTick in promise : Wenn beim Löschen der Promise-Warteschlange eine nextTick-Mikrotask angetroffen wird, wird diese sofort ausgeführt und gelöscht.
setTimeout 0 : Erklären Sie die erste Frage. Ohne nextTick und Promise oben ist die Ausführungsreihenfolge von setTimeout und setImmediate unsicher, wenn nur setTimeout und setImmediate vorhanden sind. Nachdem Sie es haben, müssen Sie bei 0 beginnen. Es ist ersichtlich, dass vor der Ausführung einer Makrowarteschlange die Mikrowarteschlangen „nextTick“ und „promise“ nacheinander überprüft und ausgeführt werden. Wenn alle Mikrowarteschlangen ausgeführt werden, ist der Zeitpunkt für setTimeout(0) reif und es wird ausgeführt.
nextTick in setTimeout : Nach der Ausführung des obigen Codes wird zuerst ein weiterer nextTick-Mikrotask ausgeführt . [Ich bin nicht sicher, ob die Mikrotasks in dieser Rückruffunktion unmittelbar nach den synchronen Tasks ausgeführt werden oder ob sie in die Mikrotask-Warteschlange gestellt und gelöscht werden, bevor die nächste Makrotask ausgeführt wird. Der Auftrag sieht aber so aus, als ob er sofort ausgeführt worden wäre. Aber ich bevorzuge Letzteres: Legen Sie sie zuerst in die Mikrotask-Warteschlange und warten Sie, und löschen Sie sie, bevor die nächste Makrotask ausgeführt wird.
setTimeout 1 : Da die Ausführung der Mikrotask Zeit in Anspruch nimmt, sind die beiden setTimeout-Timer 0 und 1 in den Timern abgelaufen, sodass beide setTimeout-Rückrufe zur Warteschlange hinzugefügt und ausgeführt wurden.
nextTick in setTimeout1 : Nach der Ausführung des obigen Codes wird sofort zuerst ein weiterer nextTick-Mikrotask ausgeführt [möglicherweise, um den Mikrotask vor dem nächsten Makrotask zu löschen].
promise in setTimeout1 : Nach der Ausführung des obigen Codes wird sofort ein weiterer Promise-Mikrotask ausgeführt [möglicherweise, um den Mikrotask vor dem nächsten Makrotask zu löschen].
setImmediate : Die Rückrufzeit der Polling-Warteschlange ist noch nicht erreicht. Daher wird zuerst die Prüfwarteschlange aufgerufen, die Warteschlange geleert und sofort der setImmediate-Rückruf ausgeführt.
nextTick in setImmediate : Nach der Ausführung des obigen Codes wird sofort ein weiterer nextTick-Mikrotask ausgeführt [möglicherweise, um den Mikrotask vor dem nächsten Makrotask zu löschen].
poll 1 : Die Poll-Warteschlange ist tatsächlich reif, der Rückruf wird ausgelöst und die synchrone Aufgabe wird ausgeführt.
nextTick in poll : Nach der Ausführung des obigen Codes wird sofort zuerst eine weitere nextTick-Mikrotask ausgeführt [möglicherweise, um die Mikrotask vor der nächsten Makrotask zu löschen].
setTimeout 100 : Wenn die Timer-Aufgabe eintrifft, wird der Rückruf ausgeführt. Und schieben Sie nextTick und Promise in die Mikrotask im Rückruf und schieben Sie den setImmediate-Rückruf in die Prüfung der Makrotask. Außerdem wurde der Timer-Thread gestartet und die Möglichkeit der nächsten Rückrufrunde zu den Timern hinzugefügt.
nextTick in setTimeout100 : Bevor die Makroaufgabe beendet wird, wird zuerst die neu hinzugefügte Mikroaufgabe im Timer-Callback - nextTick - ausgeführt. [Hier können Sie feststellen, dass es sich um den Vorgang des Löschens der Mikroaufgaben vor der nächsten Makroaufgabe handelt.]
promise in setTimeout100 : Führe den neu hinzugefügten Mikrotask sofort im Timer-Callback aus – Versprechen [lösche die Reihenfolge von nextTick und lösche dann das Versprechen]
setImmediate in setTimeout 100 : Der Grund, warum setImmediate dieses Mal vor setTimeout(0) ausgeführt wird, besteht darin, dass der Prozess von den Timern zur Prüfwarteschlange zurückgeht und bereits ein Rückruf für setImmediate vorhanden ist, der sofort ausgeführt wird.
nextTick in setImmediate in setTimeout 100 : Nach der Ausführung des obigen Codes wird eine weitere nextTick-Mikrotask ausgeführt. Die Mikrotask wird vor der nächsten Makrotask gelöscht.
setTimeout 100 - 0 : Die Abfrage kehrt erneut zu den Timern zurück und führt den Rückruf von 100-0 aus.
nextTick in setTimeout 100 - 0 : Nach der Ausführung des obigen Codes gibt es eine weitere nextTick-Mikrotask, und die Mikrotask wird vor der nächsten Makrotask gelöscht.
Erweiterung: Warum brauchen wir nextTick und Promise, wenn wir setImmediate haben?
Bei seiner ersten Entwicklung fungierte setImmediate als Mikrowarteschlange (obwohl dies nicht der Fall ist). Der Designer hofft, dass setImmediate unmittelbar nach der Ausführung der Umfrage ausgeführt wird (natürlich ist dies jetzt tatsächlich der Fall). Der Name lautet Immediate “, was立即bedeutet. Das spätere Problem besteht jedoch darin, dass sich im Poll möglicherweise N Aufgaben befinden, die kontinuierlich ausgeführt werden, und es unmöglich ist, während der Ausführung „setImmediate“ auszuführen. Da die Polling-Warteschlange nicht angehalten wird, wird der Prozess nicht nach unten ausgeführt.
So entstand nextTick, das echte Mikro-Warteschlangen-Konzept. Aber zu diesem Zeitpunkt ist der Name „immediate“ vergeben, daher lautet der Name „nextTick“ (nächster Tick). Während der Ereignisschleife wird vor der Ausführung einer Warteschlange geprüft, ob sie leer ist. Das Zweite ist „Versprechen“.
Fragen im Vorstellungsgespräch
Zum Schluss kommt noch die Interviewfrage zur Überprüfung der Lernergebnisse
asynchrone Funktion async1() {
console.log('asynchroner Start');
warte auf async2();
console.log('asynchrones Ende');
}
asynchrone Funktion async2(){
Konsole.log('async2');
}
console.log('Skriptstart');
setzeTimeout(() => {
console.log('setTimeout 0');
}, 0)
setzeTimeout(() => {
console.log('setTimeout 3');
}, 3)
setImmediate(() => {
Konsole.log('setImmediate');
})
Prozess.nextTick(() => {
console.log('nächsterTick');
})
async1();
neues Versprechen((res) => {
Konsole.log('Versprechen1');
res();
console.log('versprechen2');
}).then(() => {
console.log('Versprechen 3');
});
console.log('Skriptende');
// Die Antwort lautet wie folgt // -
// -
// -
// -
// -
// -
// -
// -
// -
// -
// -
// -
/**
Skriptstart
asynchroner Start
asynchron2
Versprechen1
Versprechen2
Skript-Ende
nächstesTick
asynchrones Ende
Versprechen 3
// Die folgenden drei Vorgänge dienen der Überprüfung der Rechengeschwindigkeit Ihres Computers.
Am schnellsten (weniger als 0 ms, um den obigen Synchronisierungscode + Mikrotask + Timervorgang auszuführen):
Sofort festlegen
setTimeout 0
setTimeout 3
Die Geschwindigkeit ist mittel (die Ausführung des obigen Synchronisierungscodes + Mikrotask + Timervorgang dauert mehr als 0 bis 3 ms):
setTimeout 0
Sofort festlegen
setTimeout 3
Schlechte Geschwindigkeit (die Ausführung des obigen Synchronisierungscodes + Mikrotask + Timervorgang dauerte mehr als 3 ms):
setTimeout 0
setTimeout 3
Sofort festlegen
*/
Mindmap
Kernphasen des Node-Lebenszyklus
Dies ist das Ende dieses Artikels zum umfassenden Verständnis der Node-Ereignisschleife. Weitere Informationen zur Node-Ereignisschleife finden Sie in früheren Artikeln auf 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, Sie werden 123WORDPRESS.COM auch in Zukunft unterstützen!
Das könnte Sie auch interessieren:
Analysieren der Knotenereignisschleife und der Nachrichtenwarteschlange
Entdecken Sie die Implementierung der Ereignisschleife des Knotens
Detaillierte Erklärung der node.js-Ereignisschleife
Beispielanalyse für Knotenereignisschleifen und Prozessmodule
Verständnis und Anwendungsbeispiele für Event Loop in node.js