Maison > interface Web > js tutoriel > le corps du texte

Une analyse approfondie de la façon de créer des processus enfants dans Node.js

青灯夜游
Libérer: 2021-10-12 10:05:48
avant
2981 Les gens l'ont consulté

Cet article vous guidera à travers le sous-processus dans Node.js et présentera les quatre méthodes de création de sous-processus dans Node.js. J'espère qu'il sera utile à tout le monde !

Une analyse approfondie de la façon de créer des processus enfants dans Node.js

Comme nous le savons tous, Node.js est un langage de programmation monothread, asynchrone et non bloquant, alors comment tirer pleinement parti des avantages des processeurs multicœurs ? Cela nécessite le module child_process pour créer un processus enfant Dans Node.js, il existe quatre façons de créer un processus enfant :

  • execexec

  • execFile

  • spawn

  • fork

【推荐学习:《nodejs 教程》】

上面四个方法都会返回 ChildProcess 实例(继承自 EventEmitter),该实例拥有三个标准的  stdio 流:

  • child.stdin

  • child.stdout

  • child.stderr

子进程生命周期内可以注册监听的事件有:

exit:子进程结束时触发,参数为 code 错误码和 signal 中断信号。

close:子进程结束并且 stdio 流被关闭时触发,参数同 exit 事件。

disconnect:父进程调用 child.disconnect() 或子进程调用 process.disconnect() 时触发。

error:子进程无法创建、或无法被杀掉、或发消息给子进程失败时触发。

message:子进程通过 process.send() 发送消息时触发。

spawn:子进程创建成功时触发(Node.js v15.1版本才添加此事件)。

execexecFile 方法还额外提供了一个回调函数,会在子进程终止的时候触发。接下来进行详细分析:

exec

exec 方法用于执行 bash 命令,它的参数是一个命令字符串。例如统计当前目录下的文件数量,用 exec 函数的写法为:

const { exec } = require("child_process")
exec("find . -type f | wc -l", (err, stdout, stderr) => {
  if (err) return console.error(`exec error: ${err}`)
  console.log(`Number of files ${stdout}`)
})
Copier après la connexion

exec 会新建一个子进程,然后缓存它的运行结果,运行结束后调用回调函数。

可能你已经想到了,exec 命令是比较危险的,假如把用户提供的字符串作为 exec 函数的参数,会面临命令行注入的风险,例如:

find . -type f | wc -l; rm -rf /;
Copier après la connexion

另外,由于 exec 会在内存中缓存全部的输出结果,当数据比较大的时候,spawn 会是更好的选择。

execFile

execFile 和 exec 的区别在于它并不会创建 shell,而是直接执行命令,所以会更高效一点,例如:

const { execFile } = require("child_process")
const child = execFile("node", ["--version"], (error, stdout, stderr) => {
  if (error) throw error
  console.log(stdout)
})
Copier après la connexion

由于没有创建 shell,程序的参数作为数组传入,因此具有较高的安全性。

spawn

spawn 函数和 execFile 类似,默认不开启 shell,但区别在于 execFile 会缓存命令行的输出,然后把结果传入回调函数中,而 spawn 则是以流的方式输出,有了流,就能非常方便的对接输入和输出了,例如典型的 wc 命令:

const child = spawn("wc")
process.stdin.pipe(child.stdin)
child.stdout.on("data", data => {
  console.log(`child stdout:\n${data}`)
})
Copier après la connexion

此时就会从命令行 stdin 获取输入,当用户触发回车 + ctrl D 时就开始执行命令,并把结果从 stdout 输出。

wc 是 Word Count 的缩写,用于统计单词数,语法为:

wc [OPTION]... [FILE]...
Copier après la connexion

如果在终端上输入 wc 命令并回车,这时候统计的是从键盘输入终端中的字符,再次按回车键,然后按 Ctrl + D 会输出统计的结果。

通过管道还可以组合复杂的命令,例如统计当前目录下的文件数量,在 Linux 命令行中会这么写:

find . -type f | wc -l
Copier après la connexion

在 Node.js 中的写法和命令行一模一样:

const find = spawn("find", [".", "-type", "f"])
const wc = spawn("wc", ["-l"])
find.stdout.pipe(wc.stdin)
wc.stdout.on("data", (data) => {
  console.log(`Number of files ${data}`)
})
Copier après la connexion

spawn 有丰富的自定义配置,例如:

const child = spawn("find . -type f | wc -l", {
  stdio: "inherit", // 继承父进程的输入输出流
  shell: true, // 开启命令行模式
  cwd: "/Users/keliq/code", // 指定执行目录
  env: { ANSWER: 42 }, // 指定环境变量(默认是 process.env)
  detached: true, // 作为独立进程存在
})
Copier après la connexion

fork

fork 函数是 spawn 函数的变体,使用 fork 创建的子进程和父进程之间会自动创建一个通信通道,子进程的全局对象 process 上面会挂载 send 方法。例如父进程 parent.js 代码:

const { fork } = require("child_process")
const forked = fork("./child.js")

forked.on("message", msg => {
  console.log("Message from child", msg);
})

forked.send({ hello: "world" })
Copier après la connexion

子进程 child.js 代码:

process.on("message", msg => {
  console.log("Message from parent:", msg)
})

let counter = 0
setInterval(() => {
  process.send({ counter: counter++ })
}, 1000)
Copier après la connexion

当调用 fork("child.js")的时候,实际上就是用 node 来执行该文件中的代码,相当于 spawn('node', ['./child.js'])

fork 的一个典型的应用场景如下:假如现在用 Node.js 创建一个 http 服务,当路由为 compute

< code>. execFile

🎜🎜spawn🎜🎜🎜fork🎜🎜【Apprentissage recommandé : " tutoriel nodejs🎜》】🎜🎜Les quatre méthodes ci-dessus renverront ChildProcess (héritée de EventEmitter), qui possède trois flux stdio standard : 🎜🎜🎜🎜child.stdin🎜🎜🎜child.stdout🎜🎜🎜child.stderr🎜🎜Événements pouvant être enregistrés pour la surveillance pendant le cycle de vie du processus enfant Il y a : 🎜🎜exit : déclenché à la fin du processus enfant, les paramètres sont le code d'erreur et le signal d'interruption du signal. 🎜🎜close : Déclenché lorsque le processus enfant se termine et que le flux stdio est fermé. Les paramètres sont les mêmes que l'événement exit. 🎜🎜disconnect : déclenché lorsque le processus parent appelle child.disconnect() ou que le processus enfant appelle process.disconnect(). 🎜🎜erreur : déclenché lorsque le processus enfant ne peut pas être créé, ne peut pas être supprimé ou ne parvient pas à envoyer un message au processus enfant. 🎜🎜message : déclenché lorsque le processus enfant envoie un message via process.send(). 🎜🎜spawn : Déclenché lorsque le processus enfant est créé avec succès (cet événement n'a été ajouté que dans Node.js v15.1). 🎜🎜Les méthodes exec et execFile fournissent également une fonction de rappel supplémentaire, qui sera déclenchée à la fin du processus enfant. Ensuite, analyse détaillée : 🎜

exec

🎜La méthode exec est utilisée pour exécuter des commandes bash, et son paramètre est une chaîne de commande. Par exemple, pour compter le nombre de fichiers dans le répertoire actuel, la fonction exec s'écrit comme suit : 🎜
const http = require("http")
const server = http.createServer()
server.on("request", (req, res) => {
  if (req.url === "/compute") {
    const sum = longComputation()
    return res.end(Sum is ${sum})
  } else {
    res.end("OK")
  }
})

server.listen(3000);
Copier après la connexion
🎜exec créera un nouveau processus enfant, puis mettra en cache ses résultats en cours d'exécution et appellera la fonction de rappel une fois l'opération terminée. 🎜🎜Peut-être avez-vous pensé que la commande exec est dangereuse. Si vous utilisez la chaîne fournie par l'utilisateur comme paramètre de la fonction exec, vous ferez face au risque d'injection de ligne de commande, telle que : 🎜
const longComputation = () => {
  let sum = 0;
  for (let i = 0; i < 1e9; i++) {
    sum += i
  }
  return sum
}
Copier après la connexion
Copier après la connexion
🎜De plus, parce que exec sera mis en cache en mémoire. Tous les résultats de sortie, lorsque les données sont relativement volumineuses, spawn sera un meilleur choix. 🎜

execFile

🎜La différence entre execFile et exec est qu'il ne crée pas de shell, mais exécute directement la commande, donc c'est plus efficace, par exemple : 🎜
const longComputation = () => {
  let sum = 0;
  for (let i = 0; i < 1e9; i++) {
    sum += i;
  }
  return sum
}

process.on("message", msg => {
  const sum = longComputation()
  process.send(sum)
})
Copier après la connexion
Copier après la connexion
🎜En raison d'aucun shell n'est créé et les paramètres du programme sont transmis sous forme de tableaux, il a donc une haute sécurité. 🎜

spawn

🎜La fonction spawn est similaire à execFile Le shell n'est pas activé par défaut, mais la différence est que execFile met en cache la sortie de la ligne de commande, puis transmet le résultat dans la fonction de rappel. Spawn produit des flux sous forme de flux, il est très pratique de connecter l'entrée et la sortie. Par exemple, la commande typique wc : 🎜
const http = require("http")
const { fork } = require("child_process")
const server = http.createServer()
server.on("request", (req, res) => {
  if (req.url === "/compute") {
    const compute = fork("compute.js")
    compute.send("start")
    compute.on("message", sum => {
      res.end(Sum is ${sum})
    })
  } else {
    res.end("OK")
  }
})
server.listen(3000)
Copier après la connexion
Copier après la connexion
🎜 sera obtenue. à partir de la ligne de commande stdin à ce moment-là, lorsque l'utilisateur déclenche Entrée + ctrl D, la commande sera exécutée et les résultats seront affichés depuis stdout. 🎜
🎜wc est l'abréviation de Word Count, qui est utilisée pour compter le nombre de mots. La syntaxe est la suivante : 🎜rrreee🎜Si vous entrez la commande wc sur le terminal et appuyez sur Entrée, les caractères saisis dans le terminal à partir de. le clavier est compté. Appuyez à nouveau. Appuyez sur la touche Entrée, puis appuyez sur Ctrl + D pour afficher les résultats statistiques. 🎜
🎜Les commandes complexes peuvent également être combinées via des tuyaux, comme compter le nombre de fichiers dans le répertoire actuel, cela sera écrit comme ceci : 🎜rrreee🎜La méthode d'écriture dans Node.js. est exactement la même que la ligne de commande : 🎜rrreee 🎜spawn a de riches configurations personnalisées, par exemple : 🎜rrreee

fork

🎜La fonction fork est une variante de spawn fonction, en utilisant fork pour créer un processus enfant et un processus parent. Un canal de communication sera automatiquement créé et la méthode d'envoi sera montée sur le processus objet global du processus enfant. Par exemple, le code parent.js du processus parent : 🎜rrreee🎜Le code enfant du processus enfant.js : 🎜rrreee🎜Lors de l'appel de fork("child.js"), le nœud est en fait utilisé pour exécuter le fichier Le code dans est équivalent à spawn('node', ['./child.js']). 🎜🎜Un scénario d'application typique de fork est le suivant : si vous utilisez Node.js pour créer un service http, lorsque la route est compute, une opération fastidieuse est effectuée. 🎜rrreee🎜Vous pouvez utiliser le code suivant pour simuler cette opération chronophage :🎜
const longComputation = () => {
  let sum = 0;
  for (let i = 0; i < 1e9; i++) {
    sum += i
  }
  return sum
}
Copier après la connexion
Copier après la connexion

那么在上线后,只要服务端收到了 compute 请求,由于 Node.js 是单线程的,耗时运算占用了 CPU,用户的其他请求都会阻塞在这里,表现出来的现象就是服务器无响应。

解决这个问题最简单的方法就是把耗时运算放到子进程中去处理,例如创建一个 compute.js 的文件,代码如下:

const longComputation = () => {
  let sum = 0;
  for (let i = 0; i < 1e9; i++) {
    sum += i;
  }
  return sum
}

process.on("message", msg => {
  const sum = longComputation()
  process.send(sum)
})
Copier après la connexion
Copier après la connexion

再把服务端的代码稍作改造:

const http = require("http")
const { fork } = require("child_process")
const server = http.createServer()
server.on("request", (req, res) => {
  if (req.url === "/compute") {
    const compute = fork("compute.js")
    compute.send("start")
    compute.on("message", sum => {
      res.end(Sum is ${sum})
    })
  } else {
    res.end("OK")
  }
})
server.listen(3000)
Copier après la connexion
Copier après la connexion

这样的话,主线程就不会阻塞,而是继续处理其他的请求,当耗时运算的结果返回后,再做出响应。其实更简单的处理方式是利用 cluster 模块,限于篇幅原因,后面再展开讲。

总结

掌握了上面四种创建子进程的方法之后,总结了以下三条规律:

  • 创建 node 子进程用 fork,因为自带通道方便通信。
  • 创建非 node 子进程用 execFile 或 spawn。如果输出内容较少用 execFile,会缓存结果并传给回调方便处理;如果输出内容多用 spawn,使用流的方式不会占用大量内存。
  • 执行复杂的、固定的终端命令用 exec,写起来更方便。但一定要记住 exec 会创建 shell,效率不如 execFile 和 spawn,且存在命令行注入的风险。

更多编程相关知识,请访问:编程视频!!

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:juejin.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal