Node hat ein völlig anderes Paradigma und sobald es richtig erfasst ist, ist es einfacher, diese andere Art der Problemlösung zu erkennen. Sie benötigen niemals mehrere Threads in einer Knotenanwendung (1), da Sie auf unterschiedliche Weise dasselbe tun. Sie erstellen mehrere Prozesse. Es unterscheidet sich jedoch erheblich von der Prefork-PMM von Apache Web Server.
Nehmen wir zunächst an, wir haben nur einen CPU-Kern und entwickeln eine Anwendung (auf Node-Art), um einige Arbeiten auszuführen. Unsere Aufgabe ist es, eine große Datei zu verarbeiten, die byteweise über ihren Inhalt läuft. Der beste Weg für unsere Software besteht darin, die Arbeit am Anfang der Datei zu beginnen und sie Byte für Byte bis zum Ende zu verfolgen.
- Hey, Hasan, ich nehme an, du bist entweder ein Neuling oder eine sehr alte Schule aus der Zeit meines Großvaters !!! Warum erstellen Sie nicht einige Threads und machen es viel schneller?
- Oh, wir haben nur einen CPU-Kern.
-- Na und? Erstellen Sie einige Threads Mann, machen Sie es schneller!
- So funktioniert es nicht. Wenn ich Threads erstelle, werde ich es langsamer machen. Weil ich dem System viel Overhead für das Wechseln zwischen Threads hinzufügen werde, um ihnen eine angemessene Zeit zu geben und innerhalb meines Prozesses zu versuchen, zwischen diesen Threads zu kommunizieren. Zusätzlich zu all diesen Fakten muss ich mir auch überlegen, wie ich einen einzelnen Job in mehrere Teile aufteilen kann, die parallel ausgeführt werden können.
- Okay, okay, ich sehe, du bist arm. Verwenden wir meinen Computer, er hat 32 Kerne!
- Wow, du bist großartig, mein lieber Freund, vielen Dank. Ich schätze es!
Dann kehren wir zur Arbeit zurück. Jetzt haben wir dank unseres reichen Freundes 32 CPU-Kerne. Die Regeln, die wir einhalten müssen, haben sich gerade geändert. Jetzt wollen wir all diesen Reichtum nutzen, den wir bekommen.
Um mehrere Kerne zu verwenden, müssen wir einen Weg finden, unsere Arbeit in Teile zu unterteilen, die wir parallel verarbeiten können. Wenn es nicht Node wäre, würden wir dafür Threads verwenden. 32 Threads, einer für jeden CPU-Kern. Da wir jedoch einen Knoten haben, werden wir 32 Knotenprozesse erstellen.
Threads können eine gute Alternative zu Node-Prozessen sein, vielleicht sogar eine bessere. aber nur in einer bestimmten Art von Arbeit, in der die Arbeit bereits definiert ist und wir die vollständige Kontrolle darüber haben, wie wir damit umgehen sollen. Abgesehen davon ist Node's Weg für jede andere Art von Problem, bei dem der Job von außen auf eine Weise kommt, über die wir keine Kontrolle haben und die wir so schnell wie möglich beantworten möchten, unbestreitbar überlegen.
- Hey, Hasan, arbeitest du immer noch mit einem Thread? Was ist los mit dir, Mann? Ich habe dir gerade das zur Verfügung gestellt, was du wolltest. Du hast keine Ausreden mehr. Erstellen Sie Threads und beschleunigen Sie sie.
- Ich habe die Arbeit in Teile geteilt und jeder Prozess wird parallel an einem dieser Teile arbeiten.
- Warum erstellst du keine Threads?
- Entschuldigung, ich denke nicht, dass es brauchbar ist. Sie können Ihren Computer mitnehmen, wenn Sie möchten?
- Nein okay, ich bin cool, ich verstehe nur nicht, warum du keine Threads verwendest?
- Danke für den Computer. :) Ich habe die Arbeit bereits in Teile geteilt und Prozesse erstellt, um diese Teile parallel zu bearbeiten. Alle CPU-Kerne werden voll ausgelastet. Ich könnte dies mit Threads anstelle von Prozessen tun; Aber Node hat diesen Weg und mein Chef Parth Thakkar möchte, dass ich Node benutze.
- Okay, lassen Sie mich wissen, wenn Sie einen anderen Computer benötigen. : p
Wenn ich 33 statt 32 Prozesse erstelle, pausiert der Scheduler des Betriebssystems einen Thread, startet den anderen, pausiert ihn nach einigen Zyklen, startet den anderen erneut ... Dies ist unnötiger Overhead. Ich will es nicht. Auf einem System mit 32 Kernen würde ich nicht einmal genau 32 Prozesse erstellen wollen, 31 können schöner sein . Weil nicht nur meine Anwendung auf diesem System funktioniert. Ein wenig Platz für andere Dinge zu lassen, kann gut sein, besonders wenn wir 32 Zimmer haben.
Ich glaube, wir sind jetzt auf der gleichen Seite, wenn es darum geht, Prozessoren für CPU-intensive Aufgaben voll auszunutzen .
- Hmm, Hasan, es tut mir leid, dass ich dich ein wenig verspottet habe. Ich glaube, ich verstehe dich jetzt besser. Aber es gibt noch etwas, für das ich eine Erklärung brauche: Worum geht es bei der Ausführung von Hunderten von Threads? Ich habe überall gelesen, dass Threads viel schneller zu erstellen und dumm sind als Forking-Prozesse. Sie verzweigen Prozesse anstelle von Threads und denken, dass dies der höchste Wert ist, den Sie mit Node erzielen würden. Ist Node dann nicht für diese Art von Arbeit geeignet?
- Keine Sorge, ich bin auch cool. Jeder sagt diese Dinge, also denke ich, ich bin es gewohnt, sie zu hören.
-- So? Knoten ist nicht gut dafür?
- Node ist dafür perfekt geeignet, obwohl Threads auch gut sein können. Wie für den Aufwand für die Thread- / Prozesserstellung; Bei Dingen, die Sie häufig wiederholen, zählt jede Millisekunde. Ich erstelle jedoch nur 32 Prozesse und es wird eine winzige Zeit dauern. Es wird nur einmal passieren. Es wird keinen Unterschied machen.
- Wann möchte ich dann Tausende von Threads erstellen?
- Sie möchten niemals Tausende von Threads erstellen. Auf einem System, das Arbeiten von außen ausführt, z. B. einem Webserver, der HTTP-Anforderungen verarbeitet. Wenn Sie für jede Anforderung einen Thread verwenden, erstellen Sie viele Threads, viele davon.
- Knoten ist aber anders? Richtig?
-- Ja genau. Hier scheint Node wirklich. Wie ein Thread viel leichter als ein Prozess ist, ist ein Funktionsaufruf viel leichter als ein Thread. Der Knoten ruft Funktionen auf, anstatt Threads zu erstellen. Im Beispiel eines Webservers verursacht jede eingehende Anforderung einen Funktionsaufruf.
-- Hmm, interessant; Sie können jedoch nur eine Funktion gleichzeitig ausführen, wenn Sie nicht mehrere Threads verwenden. Wie kann dies funktionieren, wenn viele Anfragen gleichzeitig auf dem Webserver eingehen?
- Sie haben vollkommen Recht damit, wie Funktionen einzeln ausgeführt werden, niemals zwei parallel. Ich meine, in einem einzelnen Prozess wird jeweils nur ein Codebereich ausgeführt. Der OS Scheduler pausiert diese Funktion nicht und wechselt zu einer anderen, es sei denn, er pausiert den Prozess, um einem anderen Prozess Zeit zu geben, nicht einem anderen Thread in unserem Prozess. (2)
- Wie kann ein Prozess dann zwei Anfragen gleichzeitig bearbeiten?
- Ein Prozess kann Zehntausende von Anforderungen gleichzeitig verarbeiten, solange unser System über genügend Ressourcen (RAM, Netzwerk usw.) verfügt. Wie diese Funktionen ausgeführt werden, ist DER SCHLÜSSELUNTERSCHIED.
- Hmm, sollte ich jetzt aufgeregt sein?
- Vielleicht :) Node führt eine Schleife über eine Warteschlange. In dieser Warteschlange befinden sich unsere Jobs, dh die Anrufe, mit denen wir begonnen haben, eingehende Anforderungen zu verarbeiten. Der wichtigste Punkt hierbei ist die Art und Weise, wie wir unsere Funktionen für die Ausführung entwerfen. Anstatt eine Anfrage zu bearbeiten und den Anrufer warten zu lassen, bis wir den Job beendet haben, beenden wir unsere Funktion schnell, nachdem wir eine akzeptable Menge an Arbeit erledigt haben. Wenn wir an einem Punkt angelangt sind, an dem wir auf eine andere Komponente warten müssen, um etwas zu erledigen, und uns einen Wert zurückgeben müssen, anstatt darauf zu warten, beenden wir einfach unsere Funktion und fügen den Rest der Arbeit der Warteschlange hinzu.
- Es klingt zu komplex?
- Nein, nein, ich könnte komplex klingen. Aber das System selbst ist sehr einfach und macht durchaus Sinn.
Jetzt möchte ich aufhören, den Dialog zwischen diesen beiden Entwicklern zu zitieren, und meine Antwort nach einem letzten kurzen Beispiel für die Funktionsweise dieser Funktionen beenden.
Auf diese Weise machen wir das, was OS Scheduler normalerweise tun würde. Wir unterbrechen unsere Arbeit irgendwann und lassen andere Funktionsaufrufe (wie andere Threads in einer Umgebung mit mehreren Threads) laufen, bis wir wieder an der Reihe sind. Dies ist viel besser, als die Arbeit dem OS Scheduler zu überlassen, der versucht, jedem Thread auf dem System nur Zeit zu geben. Wir wissen, was wir viel besser machen als OS Scheduler, und es wird erwartet, dass wir aufhören, wenn wir aufhören sollten.
Im Folgenden finden Sie ein einfaches Beispiel, in dem wir eine Datei öffnen und lesen, um die Daten zu bearbeiten.
Synchroner Weg:
Open File
Repeat This:
Read Some
Do the work
Asynchroner Weg:
Open File and Do this when it is ready: // Our function returns
Repeat this:
Read Some and when it is ready: // Returns again
Do some work
Wie Sie sehen, fordert unsere Funktion das System auf, eine Datei zu öffnen, und wartet nicht darauf, dass sie geöffnet wird. Es beendet sich selbst, indem es die nächsten Schritte bereitstellt, nachdem die Datei fertig ist. Bei unserer Rückkehr führt Node andere Funktionsaufrufe in der Warteschlange aus. Nachdem alle Funktionen ausgeführt wurden, wechselt die Ereignisschleife zur nächsten Runde ...
Zusammenfassend hat Node ein völlig anderes Paradigma als die Multithread-Entwicklung. das heißt aber nicht, dass es an Dingen mangelt. Bei einem synchronen Job (bei dem wir die Reihenfolge und Art der Verarbeitung festlegen können) funktioniert dies ebenso wie die Multithread-Parallelität. Für einen Job, der wie Anfragen an einen Server von außen kommt, ist er einfach überlegen.
(1) Es sei denn, Sie erstellen Bibliotheken in anderen Sprachen wie C / C ++. In diesem Fall erstellen Sie immer noch keine Threads zum Teilen von Jobs. Für diese Art von Arbeit haben Sie zwei Threads, von denen einer die Kommunikation mit Node fortsetzt, während der andere die eigentliche Arbeit erledigt.
(2) Tatsächlich hat jeder Knotenprozess aus den gleichen Gründen, die ich in der ersten Fußnote erwähnt habe, mehrere Threads. Dies ist jedoch keineswegs so, als würden 1000 Threads ähnliche Arbeiten ausführen. Diese zusätzlichen Threads dienen dazu, E / A-Ereignisse zu akzeptieren und prozessübergreifende Nachrichten zu verarbeiten.
UPDATE (Als Antwort auf eine gute Frage in Kommentaren)
@ Mark, danke für die konstruktive Kritik. In Node's Paradigma sollten Sie niemals Funktionen haben, deren Verarbeitung zu lange dauert, es sei denn, alle anderen Aufrufe in der Warteschlange sind so konzipiert, dass sie nacheinander ausgeführt werden. Bei rechenintensiven Aufgaben stellen wir bei vollständiger Betrachtung des Bildes fest, dass es sich nicht um die Frage handelt, ob Threads oder Prozesse verwendet werden sollen. aber eine Frage von "Wie können wir diese Aufgaben in ausgewogener Weise in Unteraufgaben aufteilen, die wir parallel ausführen können, indem wir mehrere CPU-Kerne auf dem System verwenden?" Angenommen, wir verarbeiten 400 Videodateien auf einem System mit 8 Kernen. Wenn wir jeweils eine Datei verarbeiten möchten, benötigen wir ein System, das verschiedene Teile derselben Datei verarbeitet. In diesem Fall ist ein Multithread-Einzelprozesssystem möglicherweise einfacher zu erstellen und noch effizienter. Wir können Node weiterhin dafür verwenden, indem wir mehrere Prozesse ausführen und Nachrichten zwischen ihnen weitergeben, wenn eine gemeinsame Nutzung / Kommunikation des Status erforderlich ist. Wie ich bereits sagte, ist ein Multiprozess-Ansatz mit Nodesowie ein Multithread-Ansatz für diese Art von Aufgaben; aber nicht mehr als das. Wie ich bereits sagte, ist die Situation, in der Node glänzt, wenn diese Aufgaben als Eingabe aus mehreren Quellen in das System eingehen, da das gleichzeitige Aufrechterhalten vieler Verbindungen in Node im Vergleich zu einem Thread pro Verbindung oder einem Prozess pro Verbindung viel leichter ist System.
Wie für setTimeout(...,0)
Anrufe; Manchmal kann es erforderlich sein, während einer zeitaufwändigen Aufgabe eine Pause einzulegen, damit Anrufe in der Warteschlange ihren Anteil an der Verarbeitung haben. Durch das Aufteilen von Aufgaben auf verschiedene Arten können Sie sich diese ersparen. Trotzdem ist dies kein wirklicher Hack, sondern nur die Art und Weise, wie Ereigniswarteschlangen funktionieren. Die Verwendung process.nextTick
für dieses Ziel ist auch viel besser, da bei der Verwendung setTimeout
Berechnungen und Überprüfungen der verstrichenen Zeit erforderlich sind, während dies process.nextTick
einfach das ist, was wir wirklich wollen: "Hey Aufgabe, gehen Sie zurück zum Ende der Warteschlange, Sie haben Ihren Anteil verwendet! ""