Wir alle wissen, dass Node.js ein ereignisgesteuertes asynchrones E/A-Modell mit einem Thread verwendet. Aufgrund seiner Eigenschaften kann es die Multi-Core-CPU nicht nutzen und einige Nicht-E/A-Vorgänge nicht gut ausführen (). Um solche Probleme zu lösen, bietet Node.js eine herkömmliche Multiprozess-(Thread-)Lösung (Diskussionen zu Prozessen und Threads finden Sie im Handbuch des Autors). Anderer Artikel Node.js und Parallelitätsmodell ), dieser Artikel führt Sie in den Multithread-Mechanismus von Node.js ein.
Wir können das Modul child_process
verwenden, um einen untergeordneten Prozess von Node.js zu erstellen, um einige spezielle Aufgaben auszuführen (z. B. das Ausführen von Skripten). Dieses Modul stellt hauptsächlich exec
, execFile
, fork
, spwan
und andere Methoden bereit . verwenden.
const { exec } = require('child_process'); exec('ls -al', (error, stdout, stderr) => { console.log(stdout); });
Diese Methode verarbeitet die Befehlszeichenfolge gemäß der durch options.shell
angegebenen ausführbaren Datei, speichert ihre Ausgabe während der Ausführung des Befehls zwischen und gibt dann das Ausführungsergebnis in Form von Rückruffunktionsparametern zurück, bis die Befehlsausführung abgeschlossen ist.
Die Parameter dieser Methode werden wie folgt erklärt:
command
: Der auszuführende Befehl (z. B. ls -al
);
options
: Parametereinstellungen (optional), die relevanten Eigenschaften lauten wie folgt:
cwd
: das aktuelle Arbeitsverzeichnis des untergeordneten Prozesses , der Standardwert ist process.cwd()
;
env
: Umgebungsvariableneinstellung (Schlüssel-Wert-Paarobjekt), der Standardwert ist der Wert von process.env
;
shell
encoding
ist: utf8
;
Datei, die Befehlszeichenfolgen verarbeitet, der Standardwert unter Unix
ist /bin/sh
, der Standardwert unter Windows
ist der Wert process.env.ComSpec
(wenn es leer ist, ist es cmd.exe
); zum Beispiel:
const { exec } = require('child_process'); exec("print('Hello World!')", { Shell: 'python' }, (error, stdout, stderr) => { console.log(stdout); });
Wenn Sie das obige Beispiel ausführen, wird Hello World!
ausgegeben, was dem Unterprozess entspricht python -c "print('Hello World!')"
ausführt. Daher müssen Sie bei der Verwendung dieses Attributs darauf achten Die Ausführung verwandter Anweisungen über die Option -c
muss unterstützt werden.
Hinweis: Es kommt vor, dass Node.js
auch die Option -c
unterstützt, diese entspricht jedoch der Option --check
. Sie wird nur verwendet, um zu erkennen, ob im angegebenen Skript Syntaxfehler vorliegen, und führt das entsprechende Skript nicht aus.
signal
: Verwenden Sie das angegebene AbortSignal, um den untergeordneten Prozess zu beenden. Dieses Attribut ist ab Version 14.17.0 verfügbar, zum Beispiel:
const { exec } = require('child_process'); const ac = new AbortController(); exec('ls -al', { signal: ac.signal }, (error, stdout, stderr) => {});
Im obigen Beispiel können wir den untergeordneten Prozess vorzeitig beenden, indem wir ac.abort()
aufrufen.
timeout
: Die Timeout-Zeit des untergeordneten Prozesses (wenn der Wert dieses Attributs größer als 0
ist, wird das durch das Attribut killSignal
angegebene Beendigungssignal an den untergeordneten Prozess gesendet, wenn die Laufzeit des untergeordneten Prozesses den angegebenen Wert überschreitet ), in Millimetern, der Standardwert ist 0
;
killSignal
maxBuffer
von 1024 * 1024
oder stderr zugelassen wird, wird der untergeordnete Prozess beendet und alle Ausgaben werden abgeschnitten
gid
gid
Beendigungssignal des untergeordneten Prozesses. SIGTERM
uid
uid
windowsHide
Windows
der Standardwert ist false
;
callback
: Rückruffunktion, einschließlich error
, stdout
, stderr
Parameter:
error
: Wenn die Befehlszeile erfolgreich ausgeführt wird, ist der Wert null
, andernfalls ist der Wert eine Instanz von Error, wobei error.code
der Exit ist Fehlercode des untergeordneten Prozesses, error.signal
ist das Signal für die Beendigung des untergeordneten Prozesses;buffer
stdout
stderr
: child stdout
und stderr
des Prozesses werden entsprechend dem Wert encoding
encoding
oder der Wert von stdout
oder stderr
eine nicht erkennbare Zeichenfolge ist, wird er entsprechend buffer
codiert.const { execFile } = require('child_process'); execFile('ls', ['-al'], (error, stdout, stderr) => { console.log(stdout); });
Die Funktion dieser Methode ähnelt exec
. Der einzige Unterschied besteht darin, dass execFile
den Befehl standardmäßig direkt mit der angegebenen ausführbaren Datei (d. h. dem Wert der file
) verarbeitet, wodurch ihre Effizienz etwas höher ist als bei exec
(Wenn Sie sich die Verarbeitungslogik der Shell ansehen, habe ich das Gefühl, dass die Effizienz vernachlässigbar ist.)
Die Parameter dieser Methode werden wie folgt erklärt:
file
: der Name oder Pfad der ausführbaren Datei;
args
: die Parameterliste der ausführbaren Datei;
options
: Parametereinstellungen (können nicht angegeben werden), die relevanten Eigenschaften sind wie folgt:
shell
: Wenn der Wert false
bedeutet dies, dass die angegebene ausführbare Datei (dh der Wert der file
) den Befehl verarbeitet. Wenn der Wert true
ist oder andere Zeichenfolgen vorliegen, entspricht die Funktion shell
in exec
. Der Standardwert ist false
;windowsVerbatimArguments
: ob die Parameter in Windows
in Unix
gesetzt oder maskiert werden sollen, und der Standardwert ist false
,encoding
Attribute cwd
, env
, timeout
, maxBuffer
, killSignal
, uid
, gid
, windowsHide
und signal
wurden oben eingeführt und werden hier nicht wiederholt.callback
: Rückruffunktion, die dem callback
in exec
entspricht und hier nicht erläutert wird.
const { fork } = require('child_process'); const echo = fork('./echo.js', { still: wahr }); echo.stdout.on('data', (data) => { console.log(`stdout: ${data}`); }); echo.stderr.on('data', (data) => { console.error(`stderr: ${data}`); }); echo.on('close', (code) => { console.log(`untergeordneter Prozess mit Code ${code} beendet`); });
Mit dieser Methode wird eine neue Node.js-Instanz erstellt, um das angegebene Node.js-Skript auszuführen und über IPC mit dem übergeordneten Prozess zu kommunizieren.
Die Parameter dieser Methode werden wie folgt erklärt:
modulePath
: der Pfad des auszuführenden Node.js-Skripts;
args
: die an das Node.js-Skript übergebene Parameterliste:
options
(können nicht angegeben werden), zugehörige Attribute Zum Beispiel: „
detached
: siehe unten für „ spwan
options.detached
;
execPath
: Erstellt die ausführbare Datei des untergeordneten Prozesses.
serialization
execArgv
process.execArgv
Die an die ausführbare Datei übergebene Zeichenfolgenparameterliste
: Der Seriennummerntyp der Interprozessnachricht, die verfügbaren Werte sind json
und advanced
, der Standardwert ist json
slient
: Wenn true
, werden stdin
, stdout
und stderr
des untergeordneten Prozesses an den übergeordneten Prozess übergeben über Pipes, andernfalls werden stdin
, stdout
und stderr
des übergeordneten Prozesses geerbt; der Standardwert ist false
:
stdio
die Beschreibung von options.stdio
in spwan
unten. Hierbei ist zu beachten, dass
slient
ignoriert werden[0, 1, 2, 'ipc']
ipc
Es wird eine Ausnahme ausgelöst.Die Eigenschaften cwd
, env
, uid
, gid
, windowsVerbatimArguments
, signal
, timeout
und killSignal
wurden oben eingeführt und werden hier nicht wiederholt.
const { spawn } = require('child_process'); const ls = spawn('ls', ['-al']); ls.stdout.on('data', (data) => { console.log(`stdout: ${data}`); }); ls.stderr.on('data', (data) => { console.error(`stderr: ${data}`); }); ls.on('close', (code) => { console.log(`untergeordneter Prozess mit Code ${code} beendet`); });
Diese Methode ist die Basismethode des child_process
-Moduls, exec
und fork
rufen schließlich spawn
auf execFile
um einen untergeordneten Prozess zu erstellen.
Die Parameter dieser Methode werden wie folgt erklärt:
command
: der Name oder Pfad der ausführbaren Datei;
args
: die an die ausführbare Datei übergebene Parameterliste: Parametereinstellungen
options
können nicht angegeben werden), die relevanten Attribute sind wie folgt:
argv0
: Wird an den untergeordneten Prozess gesendet argv[0] Wert, der Standardwert ist der Wert des Parameters command
;
detached
: Gibt an, ob der untergeordnete Prozess unabhängig vom übergeordneten Prozess ausgeführt werden soll (dh der untergeordnete Prozess wird nach dem Beenden des übergeordneten Prozesses ausgeführt). Der Prozess kann weiterhin ausgeführt werden. Der Standardwert ist false
. Wenn der Wert true
ist, hat jede Plattform folgende Auswirkungen:
Windows
Systemen kann der untergeordnete Prozess nach dem Beenden des übergeordneten Prozesses weiterhin ausgeführt werden, und der untergeordnete Prozess kann weiterhin ausgeführt werden verfügt über ein eigenes Konsolenfenster (sobald diese Funktion gestartet ist, kann sie während des laufenden Prozesses nicht geändert werden).Windows
Prozess zu diesem Zeitpunkt unabhängig davon als Leiter der neuen Prozesssitzungsgruppe Unabhängig davon, ob der untergeordnete Prozess vom übergeordneten Prozess getrennt ist, kann der untergeordnete Prozess nach dem Beenden des übergeordneten Prozesses weiter ausgeführt werden.Es ist zu beachten, dass die folgenden Punkte gleichzeitig erfüllt sein müssen, wenn der untergeordnete Prozess eine langfristige Aufgabe ausführen muss und möchte, dass der übergeordnete Prozess vorzeitig beendet wird:
unref
-Methode des untergeordneten Prozesses, um das untergeordnete Element zu entfernen Prozess aus der Ereignisschleife des übergeordneten Prozesses;true
detached
;stdio
wird ignore
.Zum Beispiel das folgende Beispiel:
// hello.js const fs = require('fs'); sei Index = 0; Funktion run() { setTimeout(() => { fs.writeFileSync('./hello', `index: ${index}`); if (index < 10) { Index += 1; laufen(); } }, 1000); } laufen(); // main.js const { spawn } = require('child_process'); const child = spawn('node', ['./hello.js'], { losgelöst: wahr, stdio: 'ignorieren' }); child.unref();
stdio
: Standard-Eingabe- und Ausgabekonfiguration des untergeordneten Prozesses, der Standardwert ist pipe
, der Wert ist eine Zeichenfolge oder ein Array:
pipe
wird in ['pipe', 'pipe', 'pipe']
umgewandelt. Die verfügbaren Werte sind pipe
, overlapped
, ignore
und inherit
.stdin
, stdout
und stderr
, jeweils Die verfügbaren Werte des Elements sind pipe
, overlapped
, ignore
, inherit
, ipc
, Stream-Objekt, positive Ganzzahl (der im übergeordneten Prozess geöffnete Dateideskriptor) und null
(falls vorhanden). Wenn es sich in den ersten drei Elementen des Arrays befindet, entspricht es pipe
“, andernfalls entspricht es ignore
), undefined
(wenn es sich in den ersten drei Elementen des Arrays befindet, entspricht es pipe
, andernfalls entspricht es „ ignore
).Die Attribute cwd
, env
, uid
, gid
, serialization
, shell
(Wert ist boolean
oder string
), windowsVerbatimArguments
, windowsHide
, signal
, timeout
, killSignal
wurden oben eingeführt und werden hier nicht wiederholt.
Das Obige gibt eine kurze Einführung in die Verwendung der Hauptmethoden im Modul child_process
. Da execSync
, execFileSync
, forkSync
und spwanSync
synchrone Versionen von exec
, execFile
und spwan
sind, gibt es keinen Unterschied in ihren Parametern sie werden nicht wiederholt.
Durch das cluster
-Modul können wir einen Node.js-Prozesscluster erstellen. Durch das Hinzufügen des Node.js-Prozesses zum Cluster können wir die Vorteile von Multicore besser nutzen und Programmaufgaben auf verschiedene Prozesse verteilen, um die Ausführung zu verbessern Effizienz des Programms; Im Folgenden werden wir die Verwendung des cluster
vorstellen:
const http = require('http'); const Cluster = require('cluster'); const numCPUs = require('os').cpus().length; if (cluster.isPrimary) { for (let i = 0; i < numCPUs; i++) { Cluster.fork(); } } anders { http.createServer((req, res) => { res.writeHead(200); res.end(`${process.pid}n`); }).listen(8000); }
Das obige Beispiel ist basierend auf der Beurteilung des cluster.isPrimary
Attributs (dh der Beurteilung, ob der aktuelle Prozess der Hauptprozess ist) in zwei Teile unterteilt:
cluster.fork
;8000
).Führen Sie das obige Beispiel aus und greifen Sie im Browser auf http://localhost:8000/
zu. Wir werden feststellen, dass pid
bei jedem Zugriff unterschiedlich ist, was zeigt, dass die Anforderung tatsächlich an jeden untergeordneten Prozess verteilt wird. Die von Node.js verwendete Standardlastausgleichsstrategie ist die Round-Robin-Planung, die über die Umgebungsvariable NODE_CLUSTER_SCHED_POLICY
oder cluster.schedulingPolicy
geändert werden kann:
NODE_CLUSTER_SCHED_POLICY = rr // oder keine Cluster.schedulingPolicy = Cluster.SCHED_RR; // oder Cluster.SCHED_NONE
Eine weitere zu beachtende Sache ist, dass, obwohl jeder untergeordnete Prozess einen HTTP-Server erstellt und denselben Port überwacht hat, dies nicht bedeutet, dass diese untergeordneten Prozesse frei konkurrieren können Benutzeranfragen, da dies nicht garantieren kann, dass die Last aller untergeordneten Prozesse ausgeglichen ist. Daher sollte der richtige Prozess darin bestehen, dass der Hauptprozess den Port abhört und die Benutzeranforderung dann zur Verarbeitung gemäß der Verteilungsrichtlinie an einen bestimmten Unterprozess weiterleitet.
Da Prozesse voneinander isoliert sind, kommunizieren Prozesse im Allgemeinen über Mechanismen wie Shared Memory, Message Passing und Pipes. Node.js vervollständigt die Kommunikation zwischen übergeordneten und untergeordneten Prozessen durch消息传递
, wie im folgenden Beispiel:
const http = require('http'); const Cluster = require('cluster'); const numCPUs = require('os').cpus().length; if (cluster.isPrimary) { for (let i = 0; i < numCPUs; i++) { const worker = cluster.fork(); worker.on('message', (message) => { console.log(`Ich bin Primary(${process.pid}), ich habe die Nachricht vom Worker erhalten: „${message}“`); worker.send(`Nachricht an Arbeiter senden`) }); } } anders { process.on('message', (message) => { console.log(`Ich bin Arbeiter(${process.pid}), ich habe die Nachricht von der Primärzentrale erhalten: „${message}“`) }); http.createServer((req, res) => { res.writeHead(200); res.end(`${process.pid}n`); Process.send('Nachricht an Primärserver senden'); }).listen(8000); }
Führen Sie das obige Beispiel aus und besuchen Sie http://localhost:8000/
. Überprüfen Sie dann das Terminal. Wir sehen eine Ausgabe ähnlich der folgenden:
Ich bin primär (44460), ich habe eine Nachricht vom Worker erhalten: „Nachricht an primär senden“ Ich bin Arbeiter (44461), ich habe die Nachricht von der Primärstelle erhalten: „Nachricht an Arbeiter senden“ Ich bin primär (44460), ich habe eine Nachricht vom Mitarbeiter erhalten: „Nachricht an primär senden“ Ich bin Arbeiter (44462), ich habe die Nachricht von der Primärzentrale erhalten: „Nachricht an Arbeiter senden“.
Mit diesem Mechanismus können wir den Status jedes untergeordneten Prozesses überwachen, sodass wir rechtzeitig eingreifen können, wenn in einem untergeordneten Prozess ein Unfall auftritt um die Verfügbarkeit der Dienste sicherzustellen.
Die Schnittstelle des cluster
Moduls ist sehr einfach gehalten. Um Platz zu sparen, machen wir hier nur einige spezielle Aussagen zur Methode cluster.setupPrimary
. Weitere Informationen finden Sie in der offiziellen Dokumentation:
cluster.setupPrimary
werden die entsprechenden Einstellungen vorgenommen wird mit dem Attribut „ cluster.settings
synchronisiert. Jeder Aufruf basiert auf dem Wert des aktuellen Attributs cluster.settings
.cluster.setupPrimary
hat dies keine Auswirkungen auf den laufenden untergeordneten Prozess, sondern nur auf nachfolgende Aufrufe cluster.fork
. sind betroffen;cluster.setupPrimary
aufgerufen wurde, wirkt es sich nicht auf nachfolgende Durchgänge zu cluster.fork
aus. Der env
Parameter des Aufrufscluster.setupPrimary
nur im Hauptprozess verwendet werden.Wir haben cluster
-Modul bereits früher eingeführt, mit dem wir einen Node.js-Prozesscluster erstellen können, um die Ausführungseffizienz des Programms zu verbessern. cluster
basiert jedoch auf dem Multiprozessmodell mit hohen Kosten beim Umschalten zwischen Prozessen und Isolation Die Erhöhung der Anzahl der untergeordneten Prozesse kann leicht dazu führen, dass aufgrund von Systemressourcenbeschränkungen nicht reagiert werden kann. Um solche Probleme zu lösen, stellt Node.js worker_threads
bereit. Im Folgenden stellen wir die Verwendung dieses Moduls anhand konkreter Beispiele kurz vor:
// server.js const http = require('http'); const { Worker } = require('worker_threads'); http.createServer((req, res) => { const httpWorker = new Worker('./http_worker.js'); httpWorker.on('message', (result) => { res.writeHead(200); res.end(`${result}n`); }); httpWorker.postMessage('Tom'); }).listen(8000); // http_worker.js const { parentPort } = require('worker_threads'); parentPort.on('message', (name) => { parentPort.postMessage(`Welcone ${name}!`); });
Das obige Beispiel zeigt die einfache Verwendung von worker_threads
. Bei der Verwendung von worker_threads
müssen Sie auf die folgenden Punkte achten:
Erstellen Sie eine Worker-Instanz über worker_threads.Worker
, wobei das Worker-Skript entweder eine unabhängige JavaScript
Datei oder字符串
sein kann Das obige Beispiel kann beispielsweise wie folgt geändert werden:
const code = "const { parentPort } = require('worker_threads'); parentPort.on('message', (name) => {parentPort.postMessage(`Welcone ${ Name}!` );})"; const httpWorker = new Worker(code, { eval: true });
Beim Erstellen einer Worker-Instanz über worker_threads.Worker
können Sie die anfänglichen Metadaten des Worker-Unterthreads festlegen, indem Sie den Wert von workerData
angeben, z. B.:
// server .js const { Worker } = require('worker_threads'); const httpWorker = new Worker('./http_worker.js', { workerData: { name: 'Tom'} }); // http_worker.js const { workerData } = require('worker_threads'); console.log(workerData);
Wenn Sie eine Worker-Instanz über worker_threads.Worker
erstellen, können Sie SHARE_ENV
festlegen, um die Notwendigkeit zu erkennen, Umgebungsvariablen zwischen dem Worker-Sub-Thread und dem Haupt-Thread zu teilen, zum Beispiel:
const { Worker, SHARE_ENV } = require('worker_threads '); const worker = new Worker('process.env.SET_IN_WORKER = "foo"', { eval: true, env: SHARE_ENV }); worker.on('exit', () => { console.log(process.env.SET_IN_WORKER); });
Im Gegensatz zum prozessübergreifenden Kommunikationsmechanismus im cluster
verwendet worker_threads
MessageChannel für die Kommunikation zwischen Threads:
parentPort.postMessage
an den Haupt-Thread und verarbeitet Nachrichten vom Haupt-Thread, indem er message
abhört message
der parentPort
-Nachricht;httpWorker
die postMessage
Methode der Worker-Sub-Thread-Instanz (hier httpWorker
und wird durch diesen Worker-Sub-Thread unten ersetzt) und verarbeitet Nachrichten vom Worker-Sub-Thread durch Abhören des message
von httpWorker
.Unabhängig davon, ob es sich in Node.js um einen vom cluster
erstellten untergeordneten Prozess oder um einen von worker_threads
erstellten untergeordneten Worker-Thread handelt, verfügen alle über eine eigene V8-Instanz und eine eigene Ereignisschleife. Der Unterschied besteht darin, dass
Obwohl es den Anschein hat, dass Worker worker_threads
Sub-Threads effizienter sind als untergeordnete Prozesse, weisen Worker-Sub- cluster
auch Mängel auf, d.
In diesem Artikel wird die Verwendung der drei Module child_process
, cluster
und worker_threads
in Node.js vorgestellt. Durch diese drei Module können wir die Vorteile von Multi-Core-CPUs voll ausnutzen und einige spezielle Probleme in einem Multi-Thread effizient lösen. Thread)-Modus. Die Betriebseffizienz von Aufgaben (wie KI, Bildverarbeitung usw.). Jedes Modul hat seine anwendbaren Szenarien. In diesem Artikel wird nur die grundlegende Verwendung erläutert, die auf Ihren eigenen Problemen basiert. Wenn in diesem Artikel Fehler enthalten sind, hoffe ich, dass Sie diese korrigieren können. Ich wünsche Ihnen allen jeden Tag viel Spaß beim Programmieren.