Ich habe persönlich den Quellcode von node.js & v8 gelesen.
Ich bin auf ein ähnliches Problem wie Sie gestoßen, als ich versucht habe, die Architektur von node.js zu verstehen, um native Module zu schreiben.
Was ich hier poste, ist mein Verständnis von node.js und dies könnte auch ein bisschen aus der Bahn geraten.
Libev ist die Ereignisschleife, die intern in node.js ausgeführt wird, um einfache Ereignisschleifenoperationen auszuführen. Es wurde ursprünglich für * nix-Systeme geschrieben. Libev bietet eine einfache, aber optimierte Ereignisschleife, auf der der Prozess ausgeführt werden kann. Mehr über libev können Sie hier lesen .
LibEio ist eine Bibliothek zur asynchronen Ausführung von Eingaben. Es behandelt Filedeskriptoren, Datenhandler, Steckdosen etc. Sie mehr darüber hier lesen hier .
LibUv ist eine Abstraktionsschicht über libeio, libev, c-ares (für DNS) und iocp (für Windows asynchronous-io). LibUv führt alle io und Ereignisse im Ereignispool aus, verwaltet und verwaltet sie. (im Falle von Libeio Threadpool). Sie sollten sich das Tutorial von Ryan Dahl auf libUv ansehen. Das wird für Sie sinnvoller, wenn es darum geht, wie libUv selbst funktioniert, und dann werden Sie verstehen, wie node.js auf libuv und v8 funktioniert.
Um nur die Javascript-Ereignisschleife zu verstehen, sollten Sie diese Videos ansehen
Um zu sehen, wie libeio mit node.js verwendet wird, um asynchrone Module zu erstellen, sollten Sie dieses Beispiel sehen .
Grundsätzlich geschieht in node.js, dass die v8-Schleife alle Javascript-Teile sowie C ++ - Module ausführt und verarbeitet [wenn sie in einem Hauptthread ausgeführt werden (gemäß der offiziellen Dokumentation ist node.js selbst Single-Threaded)]. Außerhalb des Hauptthreads behandeln libev und libeio dies im Threadpool, und libev stellt die Interaktion mit der Hauptschleife bereit. Nach meinem Verständnis hat node.js also eine permanente Ereignisschleife: das ist die v8-Ereignisschleife. Für asynchrone C ++ - Aufgaben wird ein Threadpool verwendet [via libeio & libev].
Beispielsweise:
eio_custom(Task,FLAG,AfterTask,Eio_REQUEST);
Was in allen Modulen erscheint, ruft normalerweise die Funktion Task
im Threadpool auf. Wenn es abgeschlossen ist, ruft es die AfterTask
Funktion im Hauptthread auf. Wohingegen Eio_REQUEST
der Request - Handler , der eine Struktur / Objekt , dessen Motiv sein kann , ist die Kommunikation zwischen dem Haupt - Thread und Threadpool vorzusehen.
process.nextTick
- Rufen Sie in der nächsten Schleife um die Ereignisschleife diesen Rückruf auf. Dies ist kein einfacher Alias für setTimeout (fn, 0), sondern viel effizienter. Auf welche Ereignisschleife bezieht sich das? V8-Ereignisschleife?Es sieht so aus, als hätten einige der besprochenen Entitäten (z. B. libev usw.) aufgrund der Tatsache, dass es eine Weile her ist, an Relevanz verloren, aber ich denke, die Frage hat immer noch großes Potenzial.
Lassen Sie mich versuchen, die Funktionsweise eines ereignisgesteuerten Modells anhand eines abstrakten Beispiels in einer abstrakten UNIX-Umgebung im heutigen Node-Kontext zu erklären.
Programmperspektive:
Die oben beschriebene Ereignismaschinerie wird als libuv AKA-Ereignisschleifen-Framework bezeichnet. Node nutzt diese Bibliothek, um sein ereignisgesteuertes Programmiermodell zu implementieren.
Knotenperspektive:
Während die meisten Funktionen auf diese Weise bereitgestellt werden, werden einige (asynchrone Versionen) der Dateivorgänge mithilfe zusätzlicher Threads ausgeführt, die gut in die Bibliothek integriert sind. Während Netzwerk-E / A-Vorgänge in Erwartung eines externen Ereignisses warten können, z. B. wenn der andere Endpunkt mit Daten usw. antwortet, müssen die Dateivorgänge vom Knoten selbst ausgeführt werden. Wenn Sie beispielsweise eine Datei öffnen und warten, bis der fd mit Daten fertig ist, geschieht dies nicht, da tatsächlich niemand liest! Wenn Sie im Hauptthread aus der Datei inline lesen, kann dies möglicherweise andere Aktivitäten im Programm blockieren und sichtbare Probleme verursachen, da Dateivorgänge im Vergleich zu CPU-gebundenen Aktivitäten sehr langsam sind. Daher werden interne Worker-Threads (konfigurierbar über die Umgebungsvariable UV_THREADPOOL_SIZE) verwendet, um Dateien zu bearbeiten.
Hoffe das hilft.
quelle
Eine Einführung in libuv
Auch ein Bild, das die Ereignisschleife in Node.js von @ BusyRich beschreibt
Update 05/09/2017
Gemäß diesem Dokument Node.js Ereignisschleife ,
Das folgende Diagramm zeigt eine vereinfachte Übersicht über die Reihenfolge der Operationen der Ereignisschleife.
Hinweis: Jede Box wird als "Phase" der Ereignisschleife bezeichnet.
Phasenübersicht
setTimeout()
und geplante Rückrufe ausgeführtsetInterval()
.setImmediate()
.setImmediate()
Rückrufe werden hier aufgerufen.socket.on('close', ...)
.Zwischen jedem Durchlauf der Ereignisschleife prüft Node.js, ob es auf asynchrone E / A oder Timer wartet, und fährt sauber herunter, wenn keine vorhanden sind.
quelle
In the node-v0.9.0 version of libuv libev was removed
" zitiert , aber es gibt keine Beschreibung in nodejschangelog
. github.com/nodejs/node/blob/master/CHANGELOG.md . Und wenn libev entfernt wird, wie wird dann asynchrone E / A in nodejs ausgeführt?In der NodeJs-Architektur gibt es eine Ereignisschleife.
Node.js Ereignisschleifenmodell
Knotenanwendungen werden in einem ereignisgesteuerten Single-Thread-Modell ausgeführt. Node implementiert jedoch einen Thread-Pool im Hintergrund, damit Arbeiten ausgeführt werden können.
Node.js fügt einer Ereigniswarteschlange Arbeit hinzu und lässt sie dann von einem einzelnen Thread ausführen, der eine Ereignisschleife ausführt. Die Ereignisschleife erfasst das oberste Element in der Ereigniswarteschlange, führt es aus und erfasst dann das nächste Element.
Wenn Sie Code ausführen, der länger lebt oder blockierende E / A aufweist, anstatt die Funktion direkt aufzurufen, wird die Funktion zusammen mit einem Rückruf zur Ereigniswarteschlange hinzugefügt, der nach Abschluss der Funktion ausgeführt wird. Wenn alle Ereignisse in der Ereigniswarteschlange von Node.js ausgeführt wurden, wird die Anwendung von Node.js beendet.
In der Ereignisschleife treten Probleme auf, wenn unsere Anwendungsfunktionen die E / A blockieren.
Node.js verwendet Ereignisrückrufe, um zu vermeiden, dass auf das Blockieren von E / A gewartet werden muss. Daher werden alle Anforderungen, die blockierende E / A ausführen, in einem anderen Thread im Hintergrund ausgeführt.
Wenn ein Ereignis, das E / A blockiert, aus der Ereigniswarteschlange abgerufen wird, ruft Node.js einen Thread aus dem Thread-Pool ab und führt die Funktion dort anstelle des Hauptereignisschleifenthreads aus. Dies verhindert, dass die blockierende E / A die restlichen Ereignisse in der Ereigniswarteschlange aufhält.
quelle
Es gibt nur eine Ereignisschleife, die von libuv bereitgestellt wird. V8 ist nur eine JS-Laufzeit-Engine.
quelle
Als Javascript-Anfänger hatte ich auch den gleichen Zweifel, enthält NodeJS 2 Ereignisschleifen?. Nach langer Recherche und Diskussion mit einem der V8-Mitarbeiter erhielt ich die folgenden Konzepte.
quelle
Die
pbkdf2
Funktion verfügt über die JavaScript-Implementierung, delegiert jedoch die gesamte zu erledigende Arbeit an die C ++ - Seite.Ressource: https://github.com/nodejs/node/blob/master/src/node_crypto.cc
Das Libuv-Modul hat eine weitere Verantwortung, die für einige ganz bestimmte Funktionen in der Standardbibliothek relevant ist.
Bei einigen Standard-Bibliotheksfunktionsaufrufen entscheiden sich die Node C ++ - Seite und Libuv dafür, teure Berechnungen vollständig außerhalb der Ereignisschleife durchzuführen.
Stattdessen verwenden sie einen sogenannten Thread-Pool. Der Thread-Pool besteht aus einer Reihe von vier Threads, mit denen rechenintensive Aufgaben wie die
pbkdf2
Funktion ausgeführt werden können.Standardmäßig erstellt Libuv 4 Threads in diesem Thread-Pool.
Zusätzlich zu den in der Ereignisschleife verwendeten Threads gibt es vier weitere Threads, mit denen teure Berechnungen ausgelagert werden können, die in unserer Anwendung ausgeführt werden müssen.
Viele der in der Node-Standardbibliothek enthaltenen Funktionen verwenden diesen Thread-Pool automatisch. Die
pbkdf2
Funktion ist eine davon.Das Vorhandensein dieses Thread-Pools ist sehr wichtig.
Node ist also kein Single-Thread, da es andere Threads gibt, die Node für einige rechenintensive Aufgaben verwendet.
Wenn der Ereignispool für die rechenintensive Aufgabe verantwortlich war, konnte unsere Knotenanwendung nichts anderes tun.
Unsere CPU führt alle Anweisungen einzeln in einem Thread aus.
Mithilfe des Thread-Pools können wir andere Dinge innerhalb einer Ereignisschleife ausführen, während Berechnungen durchgeführt werden.
quelle