Heim > Artikel > Web-Frontend > Eine ausführliche Analyse zum Erstellen untergeordneter Prozesse in Node.js
Dieser Artikel führt Sie durch den Unterprozess in Node.js und stellt die vier Methoden zum Erstellen von Unterprozessen in Node.js vor. Ich hoffe, dass er für alle hilfreich ist!
Wie wir alle wissen, ist Node.js eine asynchrone, nicht blockierende Single-Thread-Programmiersprache. Wie kann man also die Vorteile von Multi-Core-CPUs voll ausnutzen? Dies erfordert das Modul child_process, um einen untergeordneten Prozess zu erstellen. In Node.js gibt es vier Möglichkeiten, einen untergeordneten Prozess zu erstellen:
exec
exec
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版本才添加此事件)。
而 exec
和 execFile
方法还额外提供了一个回调函数,会在子进程终止的时候触发。接下来进行详细分析:
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}`) })
exec 会新建一个子进程,然后缓存它的运行结果,运行结束后调用回调函数。
可能你已经想到了,exec 命令是比较危险的,假如把用户提供的字符串作为 exec 函数的参数,会面临命令行注入的风险,例如:
find . -type f | wc -l; rm -rf /;
另外,由于 exec 会在内存中缓存全部的输出结果,当数据比较大的时候,spawn 会是更好的选择。
execFile 和 exec 的区别在于它并不会创建 shell,而是直接执行命令,所以会更高效一点,例如:
const { execFile } = require("child_process") const child = execFile("node", ["--version"], (error, stdout, stderr) => { if (error) throw error console.log(stdout) })
由于没有创建 shell,程序的参数作为数组传入,因此具有较高的安全性。
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}`) })
此时就会从命令行 stdin 获取输入,当用户触发回车 + ctrl D
时就开始执行命令,并把结果从 stdout 输出。
wc 是 Word Count 的缩写,用于统计单词数,语法为:
wc [OPTION]... [FILE]...如果在终端上输入 wc 命令并回车,这时候统计的是从键盘输入终端中的字符,再次按回车键,然后按
Ctrl + D
会输出统计的结果。
通过管道还可以组合复杂的命令,例如统计当前目录下的文件数量,在 Linux 命令行中会这么写:
find . -type f | wc -l
在 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}`) })
spawn 有丰富的自定义配置,例如:
const child = spawn("find . -type f | wc -l", { stdio: "inherit", // 继承父进程的输入输出流 shell: true, // 开启命令行模式 cwd: "/Users/keliq/code", // 指定执行目录 env: { ANSWER: 42 }, // 指定环境变量(默认是 process.env) detached: true, // 作为独立进程存在 })
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" })
子进程 child.js 代码:
process.on("message", msg => { console.log("Message from parent:", msg) }) let counter = 0 setInterval(() => { process.send({ counter: counter++ }) }, 1000)
当调用 fork("child.js")
的时候,实际上就是用 node 来执行该文件中的代码,相当于 spawn('node', ['./child.js'])
。
fork 的一个典型的应用场景如下:假如现在用 Node.js 创建一个 http 服务,当路由为 compute
execFile
spawn
🎜🎜🎜fork
🎜🎜【Empfohlenes Lernen: " nodejs Tutorial🎜》】🎜🎜Die oben genannten vier Methoden geben ChildProcess
-Instanz (geerbt von EventEmitter
), die über drei Standard-Stdio-Streams verfügt: 🎜🎜🎜🎜child.stdin
🎜🎜🎜child.stdout
🎜🎜🎜child.stderr
🎜🎜Ereignisse, die zur Überwachung während des Lebenszyklus des untergeordneten Prozesses registriert werden können Es gibt: 🎜🎜exit
: Wird ausgelöst, wenn der untergeordnete Prozess endet. Die Parameter sind Code-Fehlercode und Signal-Interrupt-Signal. 🎜🎜close
: Wird ausgelöst, wenn der untergeordnete Prozess endet und der stdio-Stream geschlossen wird. Die Parameter sind die gleichen wie beim exit
-Ereignis. 🎜🎜disconnect
: Wird ausgelöst, wenn der übergeordnete Prozess child.disconnect()
oder der untergeordnete Prozess process.disconnect()
aufruft. 🎜🎜error
: Wird ausgelöst, wenn der untergeordnete Prozess nicht erstellt werden kann, nicht beendet werden kann oder keine Nachricht an den untergeordneten Prozess sendet. 🎜🎜message
: Wird ausgelöst, wenn der untergeordnete Prozess eine Nachricht über process.send()
sendet. 🎜🎜spawn
: Wird ausgelöst, wenn der untergeordnete Prozess erfolgreich erstellt wurde (dieses Ereignis wurde nur in Node.js v15.1 hinzugefügt). 🎜🎜Die Methoden exec
und execFile
bieten außerdem eine zusätzliche Rückruffunktion, die ausgelöst wird, wenn der untergeordnete Prozess beendet wird. Als nächstes detaillierte Analyse: 🎜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);🎜exec erstellt einen neuen untergeordneten Prozess, speichert dann seine laufenden Ergebnisse zwischen und ruft die Rückruffunktion auf, nachdem der Vorgang abgeschlossen ist. 🎜🎜Vielleicht haben Sie gedacht, dass der Exec-Befehl relativ gefährlich ist. Wenn Sie die vom Benutzer bereitgestellte Zeichenfolge als Parameter der Exec-Funktion verwenden, besteht das Risiko einer Befehlszeileninjektion, wie zum Beispiel: 🎜
const longComputation = () => { let sum = 0; for (let i = 0; i < 1e9; i++) { sum += i } return sum }🎜Außerdem weil exec wird im Speicher zwischengespeichert Alle Ergebnisse werden ausgegeben. Wenn die Daten relativ groß sind, ist Spawn die bessere Wahl. 🎜
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) })🎜Da keine Shell erstellt wird und die Parameter des Programms als Arrays übergeben werden, ist die Sicherheit hoch. 🎜
wc
-Befehl erhalten Geben Sie zu diesem Zeitpunkt über die Befehlszeile stdin ein. Wenn der Benutzer Enter + ctrl D
auslöst, wird der Befehl ausgeführt und die Ergebnisse werden von stdout ausgegeben. 🎜🎜wc ist die Abkürzung für Word Count, die zum Zählen der Anzahl von Wörtern verwendet wird. Die Syntax lautet: 🎜🎜Komplexe Befehle können auch über Pipes kombiniert werden, z. B. das Zählen der Anzahl der Dateien im aktuellen Verzeichnis. In der Linux-Befehlszeile wird dies wie folgt geschrieben: 🎜rrreee🎜Die Schreibmethode in Node.js ist genau das gleiche wie die Befehlszeile: 🎜rrreee 🎜spawn verfügt über umfangreiche benutzerdefinierte Konfigurationen, wie zum Beispiel: 🎜rrreeeconst 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)🎜Wenn Sie den Befehl wc auf dem Terminal eingeben und die Eingabetaste drücken, werden die in das Terminal eingegebenen Zeichen ab Die Tastatureingaben werden gezählt. Drücken Sie erneut die Eingabetaste und drücken Sie dannStrg + D
, um die statistischen Ergebnisse auszugeben. 🎜
fork("child.js")
wird der Knoten tatsächlich zum Ausführen verwendet Datei Der Code in entspricht spawn('node', ['./child.js'])
. Ein typisches Anwendungsszenario von 🎜🎜fork ist wie folgt: Wenn Sie Node.js zum Erstellen eines HTTP-Dienstes verwenden und die Route compute
ist, wird ein zeitaufwändiger Vorgang ausgeführt. 🎜rrreee🎜Mit dem folgenden Code können Sie diesen zeitaufwändigen Vorgang simulieren:🎜const longComputation = () => { let sum = 0; for (let i = 0; i < 1e9; i++) { sum += i } return sum }
那么在上线后,只要服务端收到了 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) })
再把服务端的代码稍作改造:
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)
这样的话,主线程就不会阻塞,而是继续处理其他的请求,当耗时运算的结果返回后,再做出响应。其实更简单的处理方式是利用 cluster 模块,限于篇幅原因,后面再展开讲。
掌握了上面四种创建子进程的方法之后,总结了以下三条规律:
更多编程相关知识,请访问:编程视频!!
Das obige ist der detaillierte Inhalt vonEine ausführliche Analyse zum Erstellen untergeordneter Prozesse in Node.js. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!