Ich verstehe also, wie Node.js funktioniert: Es hat einen einzelnen Listener-Thread, der ein Ereignis empfängt und es dann an einen Worker-Pool delegiert. Der Arbeitsthread benachrichtigt den Listener, sobald die Arbeit abgeschlossen ist, und der Listener gibt die Antwort an den Anrufer zurück.
Meine Frage lautet: Wenn ich einen HTTP-Server in Node.js aufrichte und bei einem meiner gerouteten Pfadereignisse (z. B. "/ test / sleep") den Ruhezustand aufruft, kommt das gesamte System zum Stillstand. Sogar der einzelne Listener-Thread. Mein Verständnis war jedoch, dass dieser Code im Worker-Pool vorkommt.
Im Gegensatz dazu sind DB-Lesevorgänge eine teure E / A-Operation, wenn ich Mongoose verwende, um mit MongoDB zu sprechen. Der Knoten scheint in der Lage zu sein, die Arbeit an einen Thread zu delegieren und den Rückruf zu erhalten, wenn er abgeschlossen ist. Die zum Laden aus der Datenbank benötigte Zeit scheint das System nicht zu blockieren.
Wie entscheidet sich Node.js für die Verwendung eines Thread-Pool-Threads im Vergleich zum Listener-Thread? Warum kann ich keinen Ereigniscode schreiben, der in den Ruhezustand wechselt und nur einen Thread-Pool-Thread blockiert?
Antworten:
Ihr Verständnis der Funktionsweise von Knoten ist nicht korrekt ... aber es ist ein weit verbreitetes Missverständnis, da die Realität der Situation tatsächlich ziemlich komplex ist und sich in der Regel auf markige kleine Sätze wie "Knoten ist Single-Threaded" beschränkt, die die Dinge zu stark vereinfachen .
Im Moment werden wir explizite Multiverarbeitung / Multithreading durch Cluster- und Webworker-Threads ignorieren und nur über typische Knoten ohne Thread sprechen.
Der Knoten wird in einer einzelnen Ereignisschleife ausgeführt. Es ist Single-Threaded, und Sie bekommen immer nur diesen einen Thread. Das gesamte von Ihnen geschriebene Javascript wird in dieser Schleife ausgeführt. Wenn in diesem Code eine Blockierungsoperation ausgeführt wird, wird die gesamte Schleife blockiert, und bis zum Abschluss wird nichts anderes ausgeführt. Dies ist die typische Single-Threaded-Natur des Knotens, von der Sie so viel hören. Aber es ist nicht das ganze Bild.
Bestimmte Funktionen und Module, die normalerweise in C / C ++ geschrieben sind, unterstützen asynchrone E / A. Wenn Sie diese Funktionen und Methoden aufrufen, verwalten sie intern die Weiterleitung des Aufrufs an einen Arbeitsthread. Wenn Sie beispielsweise das
fs
Modul zum Anfordern einer Datei verwenden,fs
leitet das Modul diesen Aufruf an einen Worker-Thread weiter, und dieser Worker wartet auf seine Antwort, die er dann an die Ereignisschleife zurückgibt, die ohne sie in der Datei weitergeleitet wurde inzwischen. All dies wird von Ihnen, dem Knotenentwickler, abstrahiert, und ein Teil davon wird durch die Verwendung von libuv von den Modulentwicklern abstrahiert .Wie Denis Dollfus in den Kommentaren hervorhob (von dieser Antwort auf eine ähnliche Frage), ist die Strategie, die libuv verwendet, um asynchrone E / A zu erreichen, nicht immer ein Thread-Pool, insbesondere im Fall des
http
Moduls scheint eine andere Strategie zu sein zu diesem Zeitpunkt verwendet. Für unsere Zwecke hier ist es hauptsächlich wichtig zu beachten, wie der asynchrone Kontext erreicht wird (durch Verwendung von libuv) und dass der von libuv verwaltete Thread-Pool eine von mehreren Strategien ist, die von dieser Bibliothek angeboten werden, um Asynchronität zu erreichen.In diesem ausgezeichneten Artikel wird eine viel tiefere Analyse darüber durchgeführt, wie der Knoten Asynchronität erreicht, und einige verwandte potenzielle Probleme und wie man damit umgeht . Das meiste davon erweitert das, was ich oben geschrieben habe, aber es weist zusätzlich darauf hin:
UV_THREADPOOL_SIZE
Umgebungsvariable erhöhen , sofern Sie dies tun, bevor der Thread-Pool benötigt und erstellt wird:process.env.UV_THREADPOOL_SIZE = 10;
Wenn Sie herkömmliche Multi-Processing- oder Multithreading-Funktionen im Knoten wünschen, können Sie diese über das integrierte
cluster
Modul oder verschiedene andere Module wie die oben genannten abrufenwebworker-threads
oder sie fälschen, indem Sie eine Methode implementieren, mit der Sie Ihre Arbeit aufteilen und manuellsetTimeout
oder verwenden könnensetImmediate
oderprocess.nextTick
um Ihre Arbeit anzuhalten und in einer späteren Schleife fortzusetzen, damit andere Prozesse abgeschlossen werden können (dies wird jedoch nicht empfohlen).Bitte beachten Sie, dass Sie wahrscheinlich einen Fehler machen, wenn Sie Code mit langer Laufzeit / Blockierung in Javascript schreiben. Andere Sprachen arbeiten viel effizienter.
quelle
Das ist nicht wirklich genau. Node.js hat nur einen einzigen "Worker" -Thread, der Javascript ausführt. Es gibt Threads innerhalb des Knotens, die die E / A-Verarbeitung handhaben, aber sie als "Worker" zu betrachten, ist ein Missverständnis. Es gibt wirklich nur E / A-Behandlung und einige andere Details der internen Implementierung des Knotens, aber als Programmierer können Sie sein Verhalten nur durch einige andere Parameter wie MAX_LISTENERS beeinflussen.
In JavaScript gibt es keinen Schlafmechanismus. Wir könnten dies konkreter diskutieren, wenn Sie einen Code-Ausschnitt dessen veröffentlichen, was Ihrer Meinung nach "Schlaf" bedeutet. Es gibt keine solche Funktion, die aufgerufen werden kann, um beispielsweise etwas
time.sleep(30)
in Python zu simulieren . Es gibtsetTimeout
aber das ist grundsätzlich NICHT Schlaf.setTimeout
und die EreignisschleifesetInterval
explizit freigeben , nicht blockieren, damit andere Codebits auf dem Hauptausführungsthread ausgeführt werden können. Das einzige, was Sie tun können, ist, die CPU mit In-Memory-Berechnungen zu schleifen, wodurch der Hauptausführungsthread tatsächlich ausgehungert wird und Ihr Programm nicht mehr reagiert.Netzwerk-E / A ist immer asynchron. Ende der Geschichte. Festplatten-E / A verfügt sowohl über synchrone als auch über asynchrone APIs, sodass keine "Entscheidung" getroffen wird. node.js verhält sich gemäß den API-Kernfunktionen, die Sie als Synchronisierung bezeichnen, im Vergleich zu normaler Asynchronität. Zum Beispiel:
fs.readFile
vsfs.readFileSync
. Für Kindprozesse gibt es auch separatechild_process.exec
undchild_process.execSync
APIs.Als Faustregel gilt immer die Verwendung der asynchronen APIs. Die gültigen Gründe für die Verwendung der Synchronisierungs-APIs sind Initialisierungscode in einem Netzwerkdienst, bevor er auf Verbindungen wartet, oder einfache Skripte, die keine Netzwerkanforderungen für Build-Tools und dergleichen akzeptieren.
quelle
fs
, soweit ich weißThread-Pool wie wann und wer verwendet:
Wenn wir Node auf einem Computer verwenden / installieren, wird zunächst ein Prozess gestartet, der als Knotenprozess auf dem Computer bezeichnet wird, und er wird so lange ausgeführt, bis Sie ihn beenden. Und dieser laufende Prozess ist unser sogenannter Single Thread.
Der Mechanismus eines einzelnen Threads erleichtert das Blockieren einer Knotenanwendung. Dies ist jedoch eine der einzigartigen Funktionen, die Node.js in die Tabelle einbringt. Wenn Sie also Ihre Knotenanwendung erneut ausführen, wird sie nur in einem einzigen Thread ausgeführt. Egal, ob 1 oder 1 Million Benutzer gleichzeitig auf Ihre Anwendung zugreifen.
Lassen Sie uns also genau verstehen, was im einzelnen Thread von nodejs passiert, wenn Sie Ihre Knotenanwendung starten. Zuerst wird das Programm initialisiert, dann wird der gesamte Code der obersten Ebene ausgeführt, dh alle Codes, die sich nicht in einer Rückruffunktion befinden ( denken Sie daran, dass alle Codes in allen Rückruffunktionen in der Ereignisschleife ausgeführt werden ).
Danach wird der gesamte Modulcode ausgeführt und der gesamte Rückruf registriert. Schließlich wurde die Ereignisschleife für Ihre Anwendung gestartet.
Wie bereits erwähnt, werden alle Rückruffunktionen und Codes in diesen Funktionen in der Ereignisschleife ausgeführt. In der Ereignisschleife werden die Lasten in verschiedenen Phasen verteilt. Wie auch immer, ich werde hier nicht über die Ereignisschleife diskutieren.
Zum besseren Verständnis des Thread-Pools bitte ich Sie, sich vorzustellen, dass in der Ereignisschleife Codes innerhalb einer Rückruffunktion ausgeführt werden, nachdem die Ausführung von Codes innerhalb einer anderen Rückruffunktion abgeschlossen wurde. Wenn nun einige Aufgaben tatsächlich zu schwer sind. Sie würden dann den einzelnen Thread unseres Knotens blockieren. Und hier kommt der Thread-Pool ins Spiel, der genau wie die Ereignisschleife von der libuv-Bibliothek für Node.js bereitgestellt wird.
Der Thread-Pool ist also kein Teil von nodejs selbst. Er wird von libuv bereitgestellt, um schwere Aufgaben an libuv zu verlagern. Libuv führt diese Codes in seinen eigenen Threads aus und nach der Ausführung gibt libuv die Ergebnisse an das Ereignis in der Ereignisschleife zurück.
Der Thread-Pool gibt uns vier zusätzliche Threads, die vollständig vom einzelnen Haupt-Thread getrennt sind. Und wir können es tatsächlich bis zu 128 Threads konfigurieren.
Alle diese Threads bildeten zusammen einen Threadpool. Die Ereignisschleife kann dann schwere Aufgaben automatisch in den Thread-Pool verlagern.
Der lustige Teil ist, dass dies alles automatisch hinter den Kulissen geschieht. Es sind nicht wir Entwickler, die entscheiden, was in den Thread-Pool geht und was nicht.
Es gibt viele Aufgaben, die an den Thread-Pool gehen, wie z
quelle
Dieses Missverständnis ist lediglich der Unterschied zwischen präventivem Multitasking und kooperativem Multitasking ...
Der Schlaf schaltet den gesamten Karneval aus, weil es wirklich eine Linie zu allen Fahrten gibt und Sie das Tor geschlossen haben. Stellen Sie sich das als "JS-Interpreter und einige andere Dinge" vor und ignorieren Sie die Threads ... für Sie gibt es nur einen Thread, ...
... also blockiere es nicht.
quelle