Überblick über die ereignisgesteuerte Implementierung von Node.js
Obwohl „Ereignisse“ im ECMAScript-Standard nicht klar definiert sind, dienen Ereignisse in Browsern als äußerst wichtiger Mechanismus, der JavaScript die Möglichkeit gibt, auf Benutzeroperationen und DOM-Änderungen in Node zu reagieren Das asynchrone ereignisgesteuerte Modell ist die Grundlage für seine hohe Parallelitätsfähigkeit.
Um JavaScript zu erlernen, muss ich auch die laufende Plattform verstehen. Um das Ereignismodell von JavaScript besser zu verstehen, plane ich, mit dem Quellcode der Node- und Browser-Engine zu beginnen, die zugrunde liegende Implementierung zu analysieren und meine Analyse in einer Reihe von Blogbeiträgen zu organisieren ; Einerseits ist es eine Notiz, andererseits hoffe ich, dass ich mit allen kommunizieren kann, wenn es in der Analyse und im Verständnis Auslassungen oder Vorurteile gibt.
Eine kurze Beschreibung des ereignisgesteuerten Modells
Es gibt bereits viele gute Artikel, die das JavaScript-Ereignismodell selbst erklären. Man kann sagen, dass dies bereits ein wenig diskutiertes Thema ist. Hier werde ich nur kurz darüber schreiben und Links zu einigen guten Artikeln bereitstellen.
Wie das Programm auf Ereignisse reagiert
Unser Programm reagiert auf externe Ereignisse auf zwei Arten:
Unterbrechung
Das Betriebssystem verarbeitet Tastatur- und andere Hardwareeingaben über Interrupts. Der Vorteil dieser Methode besteht darin, dass wir unseren Code auch ohne Multithreading sicher ausführen können entsprechend Nachdem der Interrupt-Handler abgeschlossen ist, wird die Ausführungsumgebung des ursprünglichen Codes wiederhergestellt und die Ausführung wird fortgesetzt. Diese Methode erfordert Hardwareunterstützung und wird im Allgemeinen vom Betriebssystem gekapselt.
Umfrage
Schleife, um zu erkennen, ob ein Ereignis auftritt, und wenn ja, den entsprechenden Handler ausführen. Dies gilt sowohl für die Entwicklung auf niedriger als auch auf höherer Ebene.
Windows-Fensterprogramme müssen den folgenden Code in den Hauptthread schreiben, der normalerweise als Nachrichtenschleife bezeichnet wird:
MSG msg = { }; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
Die Nachrichtenschleife erkennt kontinuierlich, ob Nachrichten vorhanden sind (Benutzeroberflächenoperationen, Systemnachrichten usw.), wenn ja, verteilt sie die Nachrichten und ruft die entsprechende Rückruffunktion zur Verarbeitung auf.
Ein Nachteil der Polling-Methode besteht darin, dass das Programm nicht rechtzeitig auf neue Nachrichten reagieren kann, wenn in der Nachrichtenschleife des Hauptthreads zeitaufwändige Vorgänge ausgeführt werden. Dies ist in JavaScript offensichtlich und wird später zusammen mit seinen Lösungen erwähnt.
In JavaScript gibt es jedoch keinen ähnlichen Nachrichtenschleifencode. Wir registrieren das Ereignis einfach und warten darauf, dass es aufgerufen wird. Dies liegt daran, dass der Browser und der Knoten als Ausführungsplattformen die Ereignisschleife bereits implementiert haben und nicht in diesen Prozess eingebunden werden müssen. Er muss nur stillschweigend als Aufgerufener warten.
Ereignisschleife im Knoten
Sehen Sie sich die Implementierung der Ereignisschleife durch den Node-Quellcode an
Node verwendet V8 als JavaScript-Ausführungs-Engine und verwendet libuv, um ereignisgesteuerte asynchrone E/A zu implementieren. Die Ereignisschleife verwendet die Standardereignisschleife von libuv.
In src/node.cc,
Environment* env = CreateEnvironment( node_isolate, uv_default_loop(), context, argc, argv, exec_argc, exec_argv);
Dieser Code erstellt eine Knotenausführungsumgebung. Sie können uv_default_loop() in der libuv-Bibliothek sehen. Sie initialisiert die uv-Bibliothek selbst und die darin enthaltene default_loop_struct. Zeiger default_loop_ptr.
Anschließend lädt Node die Ausführungsumgebung, führt einige Einrichtungsvorgänge durch und startet dann die Ereignisschleife:
bool more; do { more = uv_run(env->event_loop(), UV_RUN_ONCE); if (more == false) { EmitBeforeExit(env); // Emit `beforeExit` if the loop became alive either after emitting // event, or after running some callbacks. more = uv_loop_alive(env->event_loop()); if (uv_run(env->event_loop(), UV_RUN_NOWAIT) != 0) more = true; } } while (more == true); code = EmitExit(env); RunAtExit(env); ...
Mehr wird verwendet, um zu ermitteln, ob mit dem nächsten Zyklus fortgefahren werden soll.
env->event_loop() gibt den zuvor in env gespeicherten default_loop_ptr zurück und die Funktion uv_run startet die Ereignisschleife von libuv im angegebenen UV_RUN_ONCE-Modus. In diesem Modus verarbeitet uv_run mindestens ein Ereignis: Das heißt, wenn in der aktuellen Ereigniswarteschlange keine zu verarbeitenden E/A-Ereignisse vorhanden sind, blockiert uv_run, bis ein zu verarbeitendes E/A-Ereignis vorliegt , oder der nächste Timer. Die Zeit ist abgelaufen. Wenn aktuell keine I/O-Ereignisse und keine Timer-Ereignisse vorliegen, gibt uv_run false zurück.
Der nächste Knoten entscheidet über den nächsten Schritt basierend auf der Situation von mehr:
Wenn mehr zutrifft, führen Sie die nächste Schleife weiter aus.
Wenn more falsch ist, bedeutet dies, dass keine Ereignisse auf die Verarbeitung warten. EmitBeforeExit(env) löst das Ereignis „beforeExit“ des Prozesses aus, prüft und verarbeitet die entsprechende Verarbeitungsfunktion und springt direkt aus der Schleife nach Fertigstellung.
Schließlich wird das Ereignis „exit“ ausgelöst, die entsprechende Rückruffunktion ausgeführt, der Knotenvorgang beendet und einige Ressourcenfreigabevorgänge werden später ausgeführt.
In libuv werden Timer-Ereignisse direkt in der Ereignisschleife verarbeitet, während I/O-Ereignisse in zwei Kategorien unterteilt sind:
Netzwerk-E/A nutzt die vom System bereitgestellte nicht blockierende E/A-Lösung, wie z. B. epoll unter Linux und IOCP unter Windows.
Es gibt keine (gute) Systemlösung für Dateioperationen und DNS-Operationen, daher hat libuv einen eigenen Thread-Pool erstellt, um blockierende E/A durchzuführen.
Darüber hinaus können wir die benutzerdefinierte Funktion auch zur Ausführung in den Thread-Pool werfen. Nach Abschluss des Vorgangs führt der Hauptthread diese Funktion jedoch nicht zu JavaScript hinzu Es ist nur unmöglich, neue Threads in JavaScript für die parallele Ausführung mit nativem Node zu öffnen.
Das Obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, er gefällt Ihnen allen.