Frage Wann wird der Threadpool verwendet?


So habe ich ein Verständnis davon, wie Node.js funktioniert: Es hat einen einzelnen Listener-Thread, der ein Ereignis empfängt und es dann an einen Worker-Pool delegiert. Der Worker-Thread benachrichtigt den Listener, sobald er die Arbeit abgeschlossen hat, und der Listener gibt dann die Antwort an den Aufrufer zurück.

Meine Frage ist folgende: Wenn ich einen HTTP-Server in Node.js aufstehe und Sleep auf einem meiner gerouteten Pfad-Ereignisse (wie "/ test / sleep") anrufe, kommt das ganze System zum Stillstand. Sogar der einzelne Listener-Thread. Aber mein Verständnis war, dass dieser Code im Worker-Pool passiert.

Im Gegensatz dazu, wenn ich Mongoose verwende, um mit MongoDB zu sprechen, sind DB-Lesevorgänge eine teure I / O-Operation. Node scheint in der Lage zu sein, die Arbeit an einen Thread zu delegieren und den Rückruf zu erhalten, wenn dieser abgeschlossen ist. Die Zeit, die zum Laden von der DB benötigt wird, scheint das System nicht zu blockieren.

Wie entscheidet Node.js, einen Threadpool-Thread vs. den Listener-Thread zu verwenden? Warum kann ich keinen Ereigniscode schreiben, der schläft und nur einen Thread-Thread blockiert?


75
2018-03-25 19:20


Ursprung


Antworten:


Ihr Verständnis davon, wie Knoten funktioniert, ist nicht korrekt ... aber es ist ein weit verbreiteter Irrtum, weil die Realität der Situation eigentlich ziemlich komplex ist und typischerweise zu einprägsamen kleinen Ausdrücken wie "Knoten ist single threaded" führt .

Im Moment werden explizite Multi-Processing / Multi-Threading ignoriert Cluster und Webworker-Threadsund sprechen Sie einfach über typische Knoten ohne Threads.

Der Knoten wird in einer einzelnen Ereignisschleife ausgeführt. Es ist single-threaded, und Sie bekommen nur diesen einen Thread. Das gesamte JavaScript, das Sie schreiben, wird in dieser Schleife ausgeführt, und wenn eine Blockierung in diesem Code stattfindet, wird die gesamte Schleife blockiert und nichts anderes wird passieren, bis sie beendet ist. Dies ist die typische single-threaded Natur des Knotens, von dem 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 I / O. Wenn Sie diese Funktionen und Methoden aufrufen, verwalten sie intern die Weiterleitung des Aufrufs an einen Worker-Thread. Zum Beispiel, wenn Sie das verwenden fs Modul um eine Datei anzufordern, die fs Das Modul leitet diesen Aufruf an einen Arbeitsthread weiter, und dieser Worker wartet auf seine Antwort und stellt sie dann der Ereignisschleife wieder zur Verfügung, die in der Zwischenzeit ohne es ausgeführt wurde. All dies wird von Ihnen, dem Knotenentwickler, abstrahiert, und ein Teil davon wird von den Modulentwicklern unter Verwendung von. Abstrahiert libuv.

Wie von Denis Dollfus in den Kommentaren (von diese Antwort zu einer ähnlichen Frage), ist die von libuv verwendete Strategie, um asynchrone I / O zu erreichen, nicht immer ein Thread - Pool, speziell im Falle der http Zu diesem Zeitpunkt scheint eine andere Strategie verwendet zu werden. Für unsere Zwecke ist hier vor allem wichtig zu beachten, wie der asynchrone Kontext erreicht wird (durch Verwendung von libuv) und dass der von libuv gepflegte Thread-Pool eine von mehreren Strategien ist, die von dieser Bibliothek angeboten werden, um Asynchronität zu erreichen.


Auf einer weitgehend verwandten Tangens gibt es eine viel tiefere Analyse darüber, wie Knoten Asynchronität erreicht, und einige damit verbundene potenzielle Probleme und wie man damit umgeht, in diesem ausgezeichneten Artikel. Das meiste davon erweitert sich auf das, was ich oben geschrieben habe, aber zusätzlich weist es darauf hin:

  • Jedes externe Modul, das Sie in Ihr Projekt aufnehmen, das natives C ++ und libuv verwendet, verwendet wahrscheinlich den Thread-Pool (think: Datenbankzugriff)
  • libuv hat eine Standard-Thread-Pool-Größe von 4 und verwendet eine Warteschlange, um den Zugriff auf den Thread-Pool zu verwalten - das Ergebnis ist, dass wenn Sie 5 lange laufende DB-Abfragen alle zur gleichen Zeit gehen, eine von ihnen (und alle anderen asynchronen Aktion, die auf dem Thread-Pool basiert) wird auf diese Abfragen warten, bevor sie überhaupt gestartet werden
  • Sie können dies mildern, indem Sie die Größe des Thread-Pools durch die Größe des Thread-Pools erhöhen UV_THREADPOOL_SIZE Umgebungsvariable, solange Sie es tun, bevor der Thread-Pool benötigt und erstellt wird: process.env.UV_THREADPOOL_SIZE = 10;

Wenn Sie traditionelle Multi-Processing oder Multi-Threading in Node wollen, können Sie es durch das eingebaute bekommen cluster Modul oder verschiedene andere Module wie die zuvor genannten webworker-threadsoder Sie können es fälschen, indem Sie eine Art und Weise implementieren, Ihre Arbeit zu stapeln und manuell zu verwenden setTimeout oder setImmediate oder process.nextTick pausieren Sie Ihre Arbeit und setzen Sie sie in einer späteren Schleife fort, um andere Prozesse zu beenden (aber das wird nicht empfohlen).

Bitte beachten Sie, dass Sie wahrscheinlich einen Fehler machen, wenn Sie in Javascript lange laufenden / blockierenden Code schreiben. Andere Sprachen werden viel effizienter arbeiten.


171
2018-03-25 19:44



So habe ich ein Verständnis davon, wie Node.js funktioniert: Es hat einen einzelnen Listener-Thread, der ein Ereignis empfängt und es dann an einen Worker-Pool delegiert. Der Worker-Thread benachrichtigt den Listener, sobald er die Arbeit abgeschlossen hat, und der Listener gibt dann die Antwort an den Aufrufer zurück.

Das ist nicht wirklich korrekt. Node.js hat nur einen einzigen "Worker" -Thread, der JavaScript ausführt. Es gibt Threads innerhalb des Knotens, die IO-Verarbeitung verarbeiten, aber sie als "Worker" zu betrachten, ist ein Missverständnis. Es gibt wirklich nur IO-Handling und ein paar andere Details der internen Implementierung des Knotens, aber als Programmierer kann man ihr Verhalten nicht anders als ein paar misc-Parameter wie MAX_LISTENERS beeinflussen.

Meine Frage ist folgende: Wenn ich einen HTTP-Server in Node.js aufstehe und Sleep auf einem meiner gerouteten Pfad-Ereignisse (wie "/ test / sleep") anrufe, kommt das ganze System zum Stillstand. Sogar der einzelne Listener-Thread. Aber mein Verständnis war, dass dieser Code im Worker-Pool passiert.

In JavaScript gibt es keinen Schlafmechanismus. Wir könnten dies konkreter diskutieren, wenn Sie ein Code-Snippet von dem, was Sie denken, "Schlaf" bedeutet. Es gibt keine solche Funktion, die aufgerufen wird, um etwas zu simulieren time.sleep(30) zum Beispiel in Python. Da ist setTimeout aber das ist grundsätzlich NICHT Schlaf. setTimeout und setInterval ausdrücklich FreisetzungBlockieren Sie die Ereignisschleife nicht, damit andere Codebits auf dem Hauptausführungsthread ausgeführt werden können. Die einzige Sache, die Sie tun können, ist die CPU mit In-Memory-Berechnung beschäftigt Schleife, die in der Tat die Hauptausführung Thread verhungern und Ihr Programm nicht reagieren wird.

Wie entscheidet Node.js, einen Threadpool-Thread vs. den Listener-Thread zu verwenden? Warum kann ich keinen Ereigniscode schreiben, der schläft und nur einen Thread-Thread blockiert?

Netzwerk-IO ist immer asynchron. Ende der Geschichte. Disk IO verfügt über synchrone und asynchrone APIs, daher gibt es keine "Entscheidung". node.js verhält sich entsprechend den API-Kernfunktionen, die Sie sync vs normal async nennen. Beispielsweise: fs.readFile vs fs.readFileSync. Für untergeordnete Prozesse gibt es auch separate child_process.exec und child_process.execSync APIs.

Als Faustregel gilt immer die asynchronen APIs. Die gültigen Gründe für die Verwendung der Synchronisierungs-APIs sind Initialisierungscode in einem Netzwerkdienst, der auf Verbindungen wartet, oder einfache Skripts, die keine Netzwerkanforderungen für Build-Tools und dergleichen akzeptieren.


14
2018-03-25 19:38



Dieses Missverständnis ist lediglich der Unterschied zwischen präemptivem Multitasking und kooperativem Multitasking ...

Der Schlaf schaltet den ganzen Karneval aus, denn es gibt wirklich eine Linie zu allen Fahrgeschäften und du hast das Tor geschlossen. Betrachten Sie es als "ein JS-Interpreter und einige andere Dinge" und ignorieren Sie die Threads ... für Sie gibt es nur einen Thread, ...

... also blockiere es nicht.


0
2018-04-02 22:56