javascript Nodejs Ereignisschleife



node js npm (4)

Gibt es intern zwei Ereignisschleifen in der Architektur von Nodejs?

  • Libev / libuv
  • v8 JavaScript-Ereignisschleife

Bei einer E / A-Anfrage reiht der Knoten die Anforderung an libetio ein, die wiederum die Verfügbarkeit von Daten über Ereignisse mit libev benachrichtigt, und schließlich werden diese Ereignisse von der v8-Ereignisschleife mithilfe von Rückrufen behandelt?

Grundsätzlich, wie sind Libev und Libeio in nodejs Architektur integriert?

Gibt es Dokumentation, um ein klares Bild von der internen Architektur von Nodej zu erhalten?


Answer #1

Eine Einführung in libuv

Das Projekt node.js wurde 2009 als JavaScript-Umgebung gestartet, die vom Browser abgekoppelt ist. Unter Verwendung von Googles V8 und Marc Lehmanns Libev kombinierte node.js ein Modell von I / O - ausgeglichen - mit einer Sprache, die für den Stil der Programmierung gut geeignet war; aufgrund der Art, wie es von Browsern gestaltet wurde. Da node.js immer beliebter wurde, war es wichtig, dass es unter Windows funktionierte, aber libev lief nur unter Unix. Das Windows-Äquivalent von Kernel-Ereignisbenachrichtigungsmechanismen wie kqueue oder (e) poll ist IOCP. libuv war je nach Plattform eine Abstraktion um libev oder IOCP und bot den Nutzern eine auf libev basierende API. In der Version node-v0.9.0 wurde libuv libev entfernt .

Auch ein Bild, das die Ereignisschleife in Node.js von @ BusyRich

Update 05/09/2017

Pro dieser doc Node.js Ereignisschleife ,

Das folgende Diagramm zeigt eine vereinfachte Übersicht über die Reihenfolge der Operationen der Ereignisschleife.

┌───────────────────────┐ ┌─>│ timers │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ I/O callbacks │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ idle, prepare │ │ └──────────┬────────────┘ ┌───────────────┐ │ ┌──────────┴────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └──────────┬────────────┘ │ data, etc. │ │ ┌──────────┴────────────┐ └───────────────┘ │ │ check │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ └──┤ close callbacks │ └───────────────────────┘

Hinweis: Jedes Feld wird als "Phase" der Ereignisschleife bezeichnet.

Phasenübersicht

  • Timer : Diese Phase führt Callbacks aus, die von setTimeout() und setInterval() .
  • E / A-Rückrufe : Führt fast alle Rückrufe mit Ausnahme von aus
  • schließen Rückrufe , die von Timern eingeplant werden, und setImmediate() . Leerlauf, Vorbereitung: wird nur intern verwendet.
  • Umfrage : neue E / A-Ereignisse abrufen; Knoten blockiert hier gegebenenfalls.
  • check : setImmediate() Callbacks werden hier aufgerufen.
  • Rückrufe schließen : zB socket.on('close', ...) .

Zwischen jedem Durchlauf der Ereignisschleife prüft Node.js, ob es auf asynchrone I / O oder Timer wartet und wird sauber heruntergefahren, wenn es keine gibt.


Answer #2

Einige der diskutierten Entitäten (zB: libev etc.) haben aufgrund der Tatsache, dass es eine Weile her ist, an Relevanz verloren, aber ich denke, die Frage hat noch ein großes Potenzial.

Lassen Sie mich versuchen, die Funktionsweise des ereignisgesteuerten Modells mit Hilfe eines abstrakten Beispiels in einer abstrakten UNIX-Umgebung im Kontext von Node ab heute zu erklären.

Programmperspektive:

  • Die Skript-Engine startet die Ausführung des Skripts.
  • Jedes Mal, wenn eine CPU-gebundene Operation auftritt, wird sie inline (echte Maschine) in ihrer Vollständigkeit ausgeführt.
  • Jedes Mal, wenn eine E / A-gebundene Operation auftritt, werden die Anforderung und ihr Beendigungshandler bei einer Ereignismaschine (virtuelle Maschine) registriert.
  • Wiederholen Sie die Vorgänge auf die gleiche Weise, bis das Skript endet. CPU-gebundene Operation - In-Line-Ausführung, I / O-gebundene Operationen, Anforderung an die Maschine wie oben.
  • Wenn I / O abgeschlossen ist, werden die Listener zurückgerufen.

Die obige Ereignismaschinerie wird als libuv AKA-Ereignisschleife bezeichnet. Knoten nutzt diese Bibliothek, um ihr ereignisgesteuertes Programmiermodell zu implementieren.

Knotenperspektive:

  • Haben Sie einen Thread, um die Laufzeit zu hosten.
  • Heben Sie das Benutzerskript auf.
  • Kompiliere es in native [leverage v8]
  • Laden Sie die Binärdatei und springen Sie zum Einstiegspunkt.
  • Der kompilierte Code führt die CPU-gebundenen Aktivitäten in-line unter Verwendung von Programmiergrundelementen aus.
  • Viele I / O- und Timer-Code haben native Wraps. Zum Beispiel, Netzwerk-I / O.
  • Daher werden E / A-Aufrufe vom Skript an C ++ - Bridges weitergeleitet, wobei das E / A-Handle und der Beendigungshandler als Argumente übergeben werden.
  • Der native Code übt die libuv-Schleife aus. Er erwirbt die Schleife, ordnet ein Ereignis auf niedriger Ebene, das die E / A repräsentiert, und einen nativen Rückruf-Wrapper in die Libuv-Schleifenstruktur ein.
  • Der native Code kehrt zum Skript zurück - im Moment findet keine I / O statt!
  • Die obigen Punkte werden viele Male wiederholt, bis alle Nicht-I / O-Codes ausgeführt sind und alle I / O-Codes registriert sind.
  • Wenn schließlich nichts mehr im System ausgeführt werden kann, übergeben Sie das Steuerelement an libuv
  • Libuv tritt in Aktion, es nimmt alle registrierten Ereignisse auf, fragt das Betriebssystem ab, um ihre Funktionsfähigkeit zu erhalten.
  • Diejenigen, die in einem nicht-blockierenden Modus für E / A bereit sind, werden aufgenommen, E / A durchgeführt und ihre Rückrufe ausgegeben. Einer nach dem anderen.
  • Jene, die noch nicht bereit sind (zum Beispiel ein Socket gelesen, für den der andere Endpunkt noch nichts geschrieben hat), werden solange mit dem OS getestet, bis sie verfügbar sind.
  • Die Schleife hält intern einen immer größer werdenden Timer. Wenn die Anwendung einen verzögerten Rückruf anfordert (z. B. setTimeout), wird dieser interne Zeitgeberwert verwendet, um den richtigen Zeitpunkt für das Absetzen des Rückrufs zu berechnen.

Während die meisten Funktionalitäten auf diese Weise berücksichtigt werden, werden einige (asynchrone) Versionen der Dateioperationen mit Hilfe von zusätzlichen Threads ausgeführt, die gut in die libuv integriert sind. Während Netzwerk-E / A-Operationen in Erwartung eines externen Ereignisses warten können, z. B. wenn der andere Endpunkt mit Daten usw. antwortet, benötigen die Dateioperationen etwas Arbeit vom Knoten selbst. Zum Beispiel, wenn Sie eine Datei öffnen und darauf warten, dass das fd mit Daten bereit ist, wird es nicht passieren, da niemand wirklich liest! Wenn Sie gleichzeitig aus der Datei inline im Hauptthread lesen, kann dies möglicherweise andere Aktivitäten im Programm blockieren und zu sichtbaren Problemen führen, da Dateioperationen im Vergleich zu cpu-gebundenen Aktivitäten sehr langsam sind. Daher werden interne Worker-Threads (konfigurierbar über die Umgebungsvariable UV_THREADPOOL_SIZE) verwendet, um mit Dateien zu arbeiten, während die ereignisgesteuerte Abstraktion aus der Sicht des Programms intakt funktioniert.

Hoffe das hilft.


Answer #3

Ich habe persönlich den Quellcode von node.js & v8 gelesen.

Ich bin auf ein ähnliches Problem wie Sie gestoßen, als ich versuchte, die Architektur von node.js zu verstehen, um native Module zu schreiben.

Was ich hier poste, ist mein Verständnis von node.js und das könnte auch ein bisschen daneben liegen.

  1. 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 und dennoch optimierte Ereignisschleife für den laufenden Prozess. Sie können hier mehr über libev lesen.

  2. LibEio ist eine Bibliothek zur asynchronen Ausführung der Eingabe. Es behandelt Dateideskriptoren, Datenhandler, Sockets usw. Hier können Sie mehr darüber lesen.

  3. LibUv ist eine Abstraktionsschicht an der Spitze von libeio, libev, c-ares (für DNS) und iocp (für Windows asyncronous-io). LibUv führt, verwaltet und verwaltet alle Ereignisse und Ereignisse im Ereignispool. (im Falle von libeo threadpool). Sie sollten sich Ryan Dahls Tutorial zu libUv ansehen. Das wird Ihnen mehr Sinn machen, wie libUv selbst funktioniert, und dann werden Sie verstehen, wie node.js an der Spitze von libuv und v8 arbeitet.

Um nur die JavaScript-Ereignisschleife zu verstehen, sollten Sie diese Videos betrachten

Um zu sehen, wie libeio mit node.js verwendet wird, um asynchrone Module zu erstellen, sollten Sie dieses Beispiel sehen .

Was innerhalb der node.js passiert, ist, dass die v8-Schleife alle JavaScript-Teile sowie C ++ - Module ausführt und verarbeitet (wenn sie in einem Hauptthread ausgeführt werden (laut offizieller Dokumentation ist node.js selbst single threaded)]. Außerhalb des Hauptthreads behandeln libev und litio sie im Thread-Pool und stellen die Interaktion mit der Hauptschleife bereit. Aus meiner Sicht hat node.js also eine permanente Ereignisschleife: Das ist die v8 Ereignisschleife. Um asynchrone C ++ - Tasks zu verarbeiten, verwendet es einen threadpool [via lifeio & 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. Während Eio_REQUEST der Request-Handler ist, kann es sich um eine Struktur / ein Objekt handeln, dessen Motiv es ist, eine Kommunikation zwischen dem Threadpool und dem Haupttool bereitzustellen.


Answer #4

Es gibt nur eine Ereignisschleife, die von libuv bereitgestellt wird, V8 ist nur eine JS-Laufzeit-Engine.





libev