
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.