Nous savons tous que Node.js utilise un modèle d'E/S asynchrone à thread unique et piloté par les événements. Ses caractéristiques déterminent qu'il ne peut pas tirer parti du processeur multicœur et n'est pas efficace pour effectuer certaines opérations non-E/S (. tels que l'exécution de scripts), l'informatique IA, le traitement d'images, etc.), afin de résoudre de tels problèmes, Node.js propose une solution multi-processus (thread) conventionnelle (pour les discussions sur les processus et les threads, veuillez vous référer au guide de l'auteur). autre article Node.js et Concurrency Model ), cet article vous présentera le mécanisme multi-thread de Node.js.
Nous pouvons utiliser le module child_process
pour créer un processus enfant de Node.js afin d'effectuer certaines tâches spéciales (telles que l'exécution de scripts). Ce module fournit principalement exec
, execFile
, fork
, spwan
et d'autres méthodes. . utiliser.
const { exec } = require('child_process'); exec('ls -al', (erreur, sortie standard, stderr) => { console.log(stdout); });
Cette méthode traite la chaîne de commande selon le fichier exécutable spécifié par options.shell
, met en cache sa sortie pendant l'exécution de la commande, puis renvoie le résultat de l'exécution sous la forme de paramètres de fonction de rappel jusqu'à ce que l'exécution de la commande soit terminée.
Les paramètres de cette méthode sont expliqués comme suit :
command
: la commande à exécuter (telle que ls -al
) ;
options
: paramètres (facultatifs), les propriétés pertinentes sont les suivantes :
cwd
: le répertoire de travail actuel du processus enfant , la valeur par défaut est process.cwd()
value ;
shell
env
paire clé-valeur), la valeur par défaut est la valeur de process.env
;
encoding
: codage de caractères, la valeur par défaut est : utf8
;
fichier qui traite les chaînes de commande, la valeur par défaut sous Unix
est /bin/sh
, la valeur par défaut sous Windows
est la valeur de process.env.ComSpec
(si elle est vide, c'est cmd.exe
par exemple :
const { exec);
} = require('child_process'); exec("print('Hello World!')", { shell: 'python' }, (erreur, stdout, stderr) => { console.log(stdout); });
L'exécution de l'exemple ci-dessus affichera Hello World!
ce qui équivaut au sous-processus exécutant python -c "print('Hello World!')"
. fichier exécutable spécifié. L'exécution des instructions associées via l'option -c
doit être prise en charge.
Remarque : Il arrive que Node.js
prenne également en charge l'option -c
, mais elle est équivalente à l'option --check
. Elle est uniquement utilisée pour détecter s'il y a des erreurs de syntaxe dans le script spécifié et n'exécutera pas le script concerné.
signal
: utilise le AbortSignal spécifié pour terminer le processus enfant. Cet attribut est disponible au-dessus de la version 14.17.0, par exemple :
const { exec } = require('child_process'); const ac = new AbortController(); exec('ls -al', { signal: ac.signal }, (error, stdout, stderr) => {});
Dans l'exemple ci-dessus, nous pouvons terminer le processus enfant plus tôt en appelant ac.abort()
.
timeout
: Le délai d'expiration du processus enfant (si la valeur de cet attribut est supérieure à 0
, alors lorsque le temps d'exécution du processus enfant dépasse la valeur spécifiée, le signal de fin spécifié par l'attribut killSignal
sera envoyé au processus enfant ), en millimètres, la valeur par défaut est 0
;
maxBuffer
: Le cache maximum (binaire) autorisé par 1024 * 1024
ou stderr. En cas de dépassement, le processus enfant sera tué et toute sortie sera tronquée
killSignal
: Le signal de fin du processus enfant, la valeur par défaut est SIGTERM
;
uid
: uid
pour exécuter le processus enfant ;
gid
: gid
pour exécuter le processus enfant ;
windowsHide
: s'il faut masquer la fenêtre de console du processus enfant, couramment utilisé dans les systèmes Windows
, la valeur par défaut est false
;
callback
: fonction de rappel, y compris error
, stdout
, stderr
Paramètres :
error
: Si la ligne de commande est exécutée avec succès, la valeur est null
, sinon la valeur est une instance de Error, où error.code
est la sortie code d'erreur du processus enfant, error.signal
est le signal de fin du processus enfant ;stdout
et stderr
: child stdout
et stderr
du processus sont codés en fonction de la valeur de encoding
si encoding
est buffer
. , ou si la valeur de stdout
ou stderr
est une chaîne méconnaissable, elle sera codée selon buffer
.const { execFile } = require('child_process'); execFile('ls', ['-al'], (erreur, stdout, stderr) => { console.log(stdout); });
La fonction de cette méthode est similaire à exec
. La seule différence est que execFile
traite directement la commande avec le fichier exécutable spécifié (c'est-à-dire la valeur du paramètre file
) par défaut, ce qui rend son efficacité légèrement supérieure à exec
(si vous regardez la logique de traitement du shell, j'estime que l'efficacité est négligeable).
Les paramètres de cette méthode sont expliqués comme suit :
file
: le nom ou le chemin du fichier exécutable ;
args
: la liste des paramètres du fichier exécutable
options
: paramètres des paramètres (ne peut pas être spécifié), les propriétés pertinentes sont les suivantes :
shell
: lorsque la valeur est false
cela signifie utiliser directement le fichier exécutable spécifié (c'est-à-dire la valeur du file
de paramètres ) traite la commande. Lorsque la valeur est true
ou d'autres chaînes, la fonction est équivalente shell
dans exec
. La valeur par défaut est false
;windowsVerbatimArguments
: s'il faut citer ou échapper les paramètres sous Windows
. Cet attribut sera ignoré sous Unix
, et la valeur par défaut est false
cwd
, env
, encoding
, timeout
, maxBuffer
, killSignal
, uid
, gid
, windowsHide
et signal
ont été introduits ci-dessus et ne seront pas répétés ici.callback
: fonction de callback, qui équivaut au callback
dans exec
et ne sera pas expliquée ici.
const { fork } = require('child_process'); const echo = fork('./echo.js', { silencieux : vrai }); echo.stdout.on('données', (données) => { console.log(`stdout : ${data}`); }); echo.stderr.on('données', (données) => { console.error(`stderr : ${data}`); }); echo.on('fermer', (code) => { console.log(`processus enfant terminé avec le code ${code}`); });
Cette méthode est utilisée pour créer une nouvelle instance Node.js pour exécuter le script Node.js spécifié et communiquer avec le processus parent via IPC.
Les paramètres de cette méthode sont expliqués comme suit :
modulePath
: le chemin du script Node.js à exécuter ;
args
: la liste des paramètres transmise au script Node.js
options
: paramètres des paramètres (ne peuvent pas être spécifiés), attributs associés ; tels que :
detached
: voir ci-dessous pour spwan
Description de options.detached
détaché ;
execPath
: crée le fichier exécutable du processus enfant ;
serialization
execArgv
process.execArgv
;
: Le type de numéro de série du message inter-processus, les valeurs disponibles sont json
et advanced
, la valeur par défaut est json
;
slient
: Si true
, stdin
, stdout
et stderr
du processus enfant seront transmis au processus parent via des tuyaux, sinon stdin
, stdout
et stderr
du processus parent seront hérités ; la valeur par défaut est false
stdio
: Voir la description de options.stdio
dans spwan
ci-dessous. Ce qu'il faut noter ici, c'est que
slient
sera ignorée ;ipc
doit être incluse (comme [0, 1, 2, 'ipc']
), sinon un une exception sera levée.Les propriétés cwd
, env
, uid
, gid
, windowsVerbatimArguments
, signal
, timeout
et killSignal
ont été introduites ci-dessus et ne seront pas répétées ici.
const { spawn } = require('child_process'); const ls = spawn('ls', ['-al']); ls.stdout.on('données', (données) => { console.log(`stdout : ${data}`); }); ls.stderr.on('données', (données) => { console.error(`stderr : ${data}`); }); ls.on('fermer', (code) => { console.log(`processus enfant terminé avec le code ${code}`); });
Cette méthode est la méthode de base du module child_process
exec
, execFile
et fork
appellera éventuellement spawn
pour créer un processus enfant.
Les paramètres de cette méthode sont expliqués comme suit :
command
: le nom ou le chemin du fichier exécutable ;
args
: la liste des paramètres transmise au fichier exécutable
options
: les paramètres des paramètres (ne peuvent pas être spécifiés), les attributs pertinents sont les suivants :
argv0
: valeur envoyée au processus enfant argv[0 ], la valeur par défaut est la valeur du paramètre command
;
detached
: s'il faut autoriser le processus enfant à s'exécuter indépendamment du processus parent (c'est-à-dire qu'après la sortie du processus parent, l'enfant le processus peut continuer à s'exécuter), la valeur par défaut est false
, et lorsque sa valeur est true
, chaque plate-forme L'effet est le suivant :
Windows
, une fois le processus parent terminé, le processus enfant peut continuer à s'exécuter et le processus enfant a sa propre fenêtre de console (une fois cette fonctionnalité démarrée, elle ne peut pas être modifiée pendant le processus en cours) ;Windows
, le processus enfant servira de leader du nouveau groupe de sessions de processus à ce moment-là, quoi qu'il en soit. Que le processus enfant soit séparé du processus parent, le processus enfant peut continuer à s'exécuter après la sortie du processus parent.Il convient de noter que si le processus enfant doit effectuer une tâche à long terme et souhaite que le processus parent se termine plus tôt, les points suivants doivent être respectés en même temps :
unref
du processus enfant pour supprimer l'enfant Processus à partir de la boucle d'événements du processus parent ;detached
Définir sur true
;stdio
est ignore
.Par exemple, l'exemple suivant :
// hello.js const fs = require('fs'); soit l'indice = 0 ; fonction exécuter() { setTimeout(() => { fs.writeFileSync('./hello', `index : ${index}`); si (indice < 10) { indice += 1 ; courir(); } }, 1000); } courir(); // main.js const { spawn } = require('child_process'); const enfant = spawn('node', ['./hello.js'], { détaché : vrai, stdio : 'ignorer' }); child.unref();
stdio
: configuration standard des entrées et sorties du processus enfant, la valeur par défaut est pipe
, la valeur est une chaîne ou un tableau :
pipe
est converti en ['pipe', 'pipe', 'pipe']
), les valeurs disponibles sont pipe
, overlapped
, ignore
, inherit
;stdin
, stdout
et stderr
respectivement, chacun Les valeurs disponibles de l'élément sont pipe
, overlapped
, ignore
, inherit
, ipc
, objet Stream, entier positif (le descripteur de fichier ouvert dans le processus parent), null
(s'il est situé dans les trois premiers éléments du tableau, il équivaut à pipe
, sinon il équivaut à ignore
) , undefined
(s'il est situé dans les trois premiers éléments du tableau, il équivaut à pipe
, sinon il équivaut à ignore
).Les attributs cwd
, env
, uid
, gid
, serialization
, shell
(la valeur est boolean
ou string
), windowsVerbatimArguments
, windowsHide
, signal
, timeout
, killSignal
ont été introduits ci-dessus et ne seront pas répétés ici.
Ce qui précède donne une brève introduction à l'utilisation des principales méthodes du module child_process
. Étant donné que execSync
, execFileSync
, forkSync
et spwanSync
sont des versions synchrones de exec
, execFile
et spwan
, il n'y a aucune différence dans leurs paramètres, donc ils ne se répéteront pas.
Grâce au module cluster
, nous pouvons créer un cluster de processus Node.js. En ajoutant le processus Node.js dans le cluster, nous pouvons exploiter pleinement les avantages du multicœur et distribuer les tâches du programme à différents processus pour améliorer l'exécution. efficacité du programme ; ci-dessous, nous utiliserons Cet exemple présente l'utilisation du module cluster
:
const http = require('http'); const cluster = require('cluster'); const numCPUs = require('os').cpus().length; si (cluster.isPrimary) { pour (soit i = 0; i < numCPUs; i++) { cluster.fork(); } } autre { http.createServer((req, res) => { res.writeHead(200); res.end(`${process.pid}n`); }).écouter(8000); }
L'exemple ci-dessus est divisé en deux parties basées sur le jugement de l'attribut cluster.isPrimary
(c'est-à-dire, juger si le processus actuel est le processus principal) :
cluster.fork
;8000
).Exécutez l'exemple ci-dessus et accédez http://localhost:8000/
dans le navigateur. Nous constaterons que pid
renvoyé est différent pour chaque accès, ce qui montre que la requête est bien distribuée à chaque processus enfant. La stratégie d'équilibrage de charge par défaut adoptée par Node.js est la planification circulaire, qui peut être modifiée via la variable d'environnement NODE_CLUSTER_SCHED_POLICY
ou cluster.schedulingPolicy
:
NODE_CLUSTER_SCHED_POLICY = rr // ou none cluster.schedulingPolicy = cluster.SCHED_RR; // ou cluster.SCHED_NONE
Une autre chose à noter est que même si chaque processus enfant a créé un serveur HTTP et écouté le même port, cela ne signifie pas que ces processus enfants sont libres de rivaliser. demandes des utilisateurs, car cela ne peut pas garantir que la charge de tous les processus enfants est équilibrée. Par conséquent, le processus correct devrait être que le processus principal écoute le port, puis transmette la demande de l'utilisateur à un sous-processus spécifique pour traitement conformément à la politique de distribution.
Étant donné que les processus sont isolés les uns des autres, ils communiquent généralement via des mécanismes tels que la mémoire partagée, la transmission de messages et les canaux. Node.js complète la communication entre les processus parent et enfant via消息传递
, comme dans l'exemple suivant :
const http = require('http'); const cluster = require('cluster'); const numCPUs = require('os').cpus().length; si (cluster.isPrimary) { pour (soit i = 0; i < numCPUs; i++) { const travailleur = cluster.fork(); travailleur.on('message', (message) => { console.log(`Je suis principal (${process.pid}), j'ai reçu un message du travailleur : "${message}"`); travailleur.send(`Envoyer un message au travailleur`) }); } } autre { processus.on('message', (message) => { console.log(`Je suis un travailleur(${process.pid}), j'ai reçu un message du primaire : "${message}"`) }); http.createServer((req, res) => { res.writeHead(200); res.end(`${process.pid}n`); process.send('Envoyer le message au primaire'); }).écouter(8000); }
Exécutez l'exemple ci-dessus et visitez http://localhost:8000/
, puis vérifiez le terminal, nous verrons un résultat similaire à celui-ci :
Je suis principal (44460), j'ai reçu un message du travailleur : "Envoyer un message au principal" Je suis un travailleur (44461), j'ai reçu un message du principal : "Envoyer un message au travailleur" Je suis le principal (44460), j'ai reçu un message du travailleur : "Envoyer un message au principal" Je suis un travailleur (44462), j'ai reçu un message du primaire : "Envoyer un message au travailleur".
Grâce à ce mécanisme, nous pouvons surveiller l'état de chaque processus enfant afin que lorsqu'un accident survient dans un processus enfant, nous puissions y intervenir à temps. pour garantir la disponibilité des services.
L'interface du module cluster
est très simple. Afin d'économiser de l'espace, nous faisons ici uniquement quelques déclarations spéciales sur la méthode cluster.setupPrimary
. Pour les autres méthodes, veuillez consulter la documentation officielle :
cluster.setupPrimary
, les paramètres pertinents. sera synchronisé avec l'attribut cluster.settings
, et chaque appel est basé sur la valeur de l'attribut cluster.settings
actuel ;cluster.setupPrimary
, il n'a aucun impact sur le processus enfant en cours d'exécution, uniquement les appels cluster.fork
suivants. sont affectés ;cluster.setupPrimary
, cela n'affecte pas les passes ultérieures à cluster.fork
Le paramètre env
de l'appelcluster.setupPrimary
ne peut être utilisé que dans le processus principal.Nous avons introduit cluster
plus tôt, grâce auquel nous pouvons créer un cluster de processus Node.js pour améliorer l'efficacité de l'exécution du programme. Cependant, cluster
est basé sur le modèle multi-processus, avec une commutation coûteuse entre les processus et une isolation. de ressources entre les processus. L'augmentation du nombre de processus enfants peut facilement conduire au problème de l'incapacité de répondre en raison des contraintes de ressources du système. Pour résoudre de tels problèmes, Node.js fournit worker_threads
. Ci-dessous, nous présentons brièvement l'utilisation de ce module à travers des exemples spécifiques :
// server.js. const http = exiger('http'); const {Travailleur} = require('worker_threads'); http.createServer((req, res) => { const httpWorker = new Worker('./http_worker.js'); httpWorker.on('message', (résultat) => { res.writeHead(200); res.end(`${result}n`); }); httpWorker.postMessage('Tom'); }).écouter(8000); // http_worker.js const { parentPort } = require('worker_threads'); parentPort.on('message', (nom) => { parentPort.postMessage(`Welcone ${name}!`); });
L'exemple ci-dessus montre l'utilisation simple de worker_threads
. Lorsque vous utilisez worker_threads
, vous devez faire attention aux points suivants :
Créez une instance Worker via worker_threads.Worker
, où le script Worker peut être soit un fichier JavaScript
indépendant, soit字符串
, par exemple, l'exemple ci-dessus peut être modifié comme suit :
const code = "const { parentPort } = require('worker_threads'); parentPort.on('message', (name) => {parentPort.postMessage(`Welcone ${ nom}!` );})"; const httpWorker = new Worker(code, { eval: true });
Lors de la création d'une instance Worker via worker_threads.Worker
, vous pouvez définir les métadonnées initiales du sous-thread Worker en spécifiant la valeur de workerData
, telle que :
// serveur .js const {Travailleur} = require('worker_threads'); const httpWorker = new Worker('./http_worker.js', { workerData : { nom : 'Tom'} }); // http_worker.js const { travailleurData } = require('worker_threads'); console.log(workerData);
Lors de la création d'une instance Worker via worker_threads.Worker
, vous pouvez définir SHARE_ENV
pour réaliser la nécessité de partager des variables d'environnement entre le sous-thread Worker et le thread principal, par exemple :
const { Worker, SHARE_ENV } = require('worker_threads'); const worker = new Worker('process.env.SET_IN_WORKER = "foo"', { eval : true, env : SHARE_ENV }); travailleur.on('exit', () => { console.log(process.env.SET_IN_WORKER); });
Différent du mécanisme de communication inter-processus dans cluster
, worker_threads
utilise MessageChannel pour communiquer entre les threads :
parentPort.postMessage
et traite les messages du thread principal en écoutant message
événement de message
du message parentPort
;httpWorker
via la méthode postMessage
de l'instance de sous-thread Worker (ici httpWorker
, et est remplacé par ce sous-thread Worker ci-dessous) et traite les messages du sous-thread Worker en écoutant l'événement message
de httpWorker
.Dans Node.js, qu'il s'agisse d'un processus enfant créé par cluster
ou d'un thread enfant Worker créé par worker_threads
, ils ont tous leur propre instance V8 et leur propre boucle d'événements. La différence est que
Bien qu'il semble que les sous-threads de travail soient plus efficaces que les processus enfants, les sous-threads de travail présentent également des inconvénients, c'est-à-dire que cluster
fournit l'équilibrage de charge, tandis que worker_threads
nous oblige à terminer la conception et la mise en œuvre de l'équilibrage de charge par nous-mêmes.
Cet article présente l'utilisation des trois modules child_process
, cluster
et worker_threads
dans Node.js, grâce à ces trois modules, nous pouvons tirer pleinement parti des avantages des processeurs multicœurs et résoudre efficacement certains problèmes particuliers dans un multi-thread (. thread). L’efficacité opérationnelle des tâches (telles que l’IA, le traitement d’images, etc.). Chaque module a ses scénarios applicables. Cet article explique uniquement son utilisation de base. Comment l'utiliser efficacement en fonction de vos propres problèmes doit encore être exploré par vous-même. Enfin, s’il y a des erreurs dans cet article, j’espère que vous pourrez les corriger. Je vous souhaite à tous un bon codage chaque jour.