這篇文章帶大家了解一下Node.js中的子進程,介紹Node.js中創建子進程的四種方法,希望對大家有幫助!
眾所周知,Node.js 是單執行緒、非同步非阻塞的程式語言,那該如何充分利用多核心 CPU 的優勢呢?這就需要用到child_process 模組來建立子進程了,在Node.js 中,有四種方法可以建立子進程:
##exec
execFile
#spawn
實例(繼承自EventEmitter
),此實例有三個標準的 stdio 串流:
exit
:子程序結束時觸發,參數為code 錯誤碼和signal 中斷訊號。close
exit 事件。
disconnect
child.disconnect() 或子程序呼叫
process.disconnect() 時觸發。
error
message
process.send() 發送訊息時觸發。
spawn
而
exec
execFile 方法也額外提供了一個回呼函數,會在子程序終止的時候觸發。接下來進行詳細分析:
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}`) })
find . -type f | wc -l; rm -rf /;另外,由於exec 會在記憶體中快取全部的輸出結果,當資料比較大的時候,spawn 會是更好的選擇。 execFileexecFile 和exec 的差別在於它並不會建立shell,而是直接執行指令,所以會更有效率一點,例如:
const { execFile } = require("child_process") const child = execFile("node", ["--version"], (error, stdout, stderr) => { if (error) throw error console.log(stdout) })
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 指令並回車,這時候統計的是從鍵盤輸入終端機中的字符,再次按回車鍵,然後按在Node.js 中的寫法和命令列一模一樣:Ctrl D
會輸出統計的結果。
透過管道還可以組合複雜的命令,例如統計當前目錄下的檔案數量,在Linux 命令列中會這麼寫:
find . -type f | wc -l
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, // 作为独立进程存在 })forkfork 函數是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" })
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
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);
可以用下面的程式碼來模擬該耗時的運算:
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 模块,限于篇幅原因,后面再展开讲。
掌握了上面四种创建子进程的方法之后,总结了以下三条规律:
更多编程相关知识,请访问:编程视频!!
以上是深入淺析Node.js中建立子進程的方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!