Implementierung des Timers in Node.js
Wie im vorherigen Blogbeitrag erwähnt, wird der Timer in Node nicht durch das Öffnen eines neuen Threads implementiert, sondern direkt in der Ereignisschleife. Im Folgenden werden mehrere JavaScript-Timer-Beispiele und Node-bezogener Quellcode verwendet, um zu analysieren, wie die Timer-Funktion in Node implementiert wird.
Funktionen der Timer-Funktion in JavaScript
Ob es sich um Node oder den Browser handelt, es gibt zwei Timer-Funktionen, setTimeout und setInterval, und ihre Arbeitseigenschaften sind grundsätzlich gleich, daher wird im Folgenden nur Node als Beispiel für die Analyse verwendet.
Wir wissen, dass sich der Timer in JavaScript nicht vom zugrunde liegenden geplanten Interrupt des Computers unterscheidet. Wenn ein Interrupt eintrifft, wird der aktuell ausgeführte Code unterbrochen und an die geplante Interrupt-Verarbeitungsfunktion übergeben. Wenn der JavaScript-Timer abläuft und im aktuellen Ausführungsthread kein Code ausgeführt wird, wird die entsprechende Rückruffunktion ausgeführt. Wenn derzeit Code ausgeführt wird, unterbricht die JavaScript-Engine weder den aktuellen Code, um den Rückruf auszuführen start Der neue Thread führt den Rückruf aus, wird jedoch verarbeitet, nachdem der aktuelle Code ausgeführt wurde.
console.time('A') setTimeout(function () { console.timeEnd('A'); }, 100); var i = 0; for (; i < 100000; i++) { }
Wenn Sie den obigen Code ausführen, können Sie sehen, dass die endgültige Ausgabezeit nicht etwa 100 ms, sondern einige Sekunden beträgt. Dies zeigt, dass die geplante Callback-Funktion tatsächlich nicht ausgeführt wird, bevor die Schleife abgeschlossen ist, sondern bis zum Ende der Schleife verschoben wird. Tatsächlich können während der Ausführung von JavaScript-Code nicht alle Ereignisse verarbeitet werden, und neue Ereignisse müssen verarbeitet werden, bis der aktuelle Code abgeschlossen ist. Aus diesem Grund reagiert der Browser nicht mehr, wenn darin zeitaufwändiger JavaScript-Code ausgeführt wird. Um mit dieser Situation umzugehen, können wir die Yielding Processes-Technik verwenden, um den zeitaufwändigen Code in kleine Blöcke (Chunks) aufzuteilen, nach der Verarbeitung jedes Blocks einmal setTimeout auszuführen und nach einer kurzen Zeitspanne zuzustimmen, den nächsten Block zu verarbeiten Während dieser Zeit kann der Browser/Knoten in der Leerlaufzeit Ereignisse in der Warteschlange verarbeiten.
Ergänzende Informationen
Erweiterte Timer und Yielding-Prozesse werden ausführlicher in Kapitel 22 „Fortgeschrittene Techniken der fortgeschrittenen JavaScript-Programmierung“, dritte Ausgabe, besprochen.
Timer-Implementierung im Knoten
libuvs Initialisierung des Typs uv_loop_t
Im vorherigen Blog-Beitrag wurde erwähnt, dass Node die uv_run-Funktion von libuv aufruft, um default_loop_ptr für die Ereignisplanung zu starten. default_loop_ptr verweist auf eine Variable default_loop_struct vom Typ uv_loop_t. Wenn der Knoten startet, ruft er uv_loop_init(&default_loop_struct) auf, um ihn zu initialisieren. Der Auszug der Funktion uv_loop_init lautet wie folgt:
int uv_loop_init(uv_loop_t* loop) { ... loop->time = 0; uv_update_time(loop); ... }
Sie können sehen, dass dem Zeitfeld der Schleife zuerst der Wert 0 zugewiesen wird und dann die Funktion uv_update_time aufgerufen wird, die loop.time die neueste Zählzeit zuweist.
Nach Abschluss der Initialisierung hat default_loop_struct.time einen Anfangswert und zeitbezogene Vorgänge werden mit diesem Wert verglichen, um zu bestimmen, ob die entsprechende Rückruffunktion aufgerufen werden soll.
libuvs Ereignisplanungskern
Wie bereits erwähnt, ist die Funktion uv_run der Kernbestandteil der libuv-Bibliothek zur Implementierung der Ereignisschleife. Das Folgende ist ihr Flussdiagramm:
Hier ist eine kurze Beschreibung der obigen Logik im Zusammenhang mit dem Timer:
Aktualisieren Sie das Zeitfeld der aktuellen Schleife, das das „Jetzt“ unter dem aktuellen Schleifenkonzept markiert
Wenn ein I/O-Ereignis auftritt, wird der entsprechende Rückruf ausgeführt. Da während der Rückrufausführungszeit möglicherweise ein anderer Timer abgelaufen ist, muss der Timer erneut überprüft und der Rückruf ausgeführt werden.
(Eigentlich ist (4.) hier komplizierter und nicht nur ein einstufiger Vorgang. Diese Beschreibung dient nur dazu, keine weiteren Details einzubeziehen und sich auf die Implementierung des Timers zu konzentrieren.)
timer_wrap und Timer im Knoten
Es gibt eine TimerWrap-Klasse in Node, die als timer_wrap-Modul in Node registriert ist.
NODE_MODULE_CONTEXT_AWARE_BUILTIN(timer_wrap, node::TimerWrap::Initialize)Die TimerWrap-Klasse ist im Grunde eine direkte Kapselung von uv_timer_t, und NODE_MODULE_CONTEXT_AWARE_BUILTIN ist ein Makro, das von Node zum Registrieren integrierter Module verwendet wird.
Nach diesem Schritt kann JavaScript dieses Modul abrufen und bedienen. Die Datei src/lib/timers.js verwendet JavaScript, um die Funktion timer_wrap zu kapseln und exportiert exports.setTimeout, exports.setInterval, exports.setImmediate und andere Funktionen.
Knotenstart und globale Initialisierung
Im vorherigen Artikel wurde erwähnt, dass Node beim Start die Ausführungsumgebung LoadEnvironment(env) lädt. Ein sehr wichtiger Schritt in dieser Funktion ist das Laden von src/node.js und das Laden der angegebenen Modul Und global und Prozess initialisieren. Natürlich werden auch Funktionen wie setTimeout über src/node.js an das globale Objekt gebunden.
Das Obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, er gefällt Ihnen allen.