這篇文章主要講解了“Node.js中創(chuàng)建子進程的方法有哪些”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Node.js中創(chuàng)建子進程的方法有哪些”吧!
創(chuàng)新互聯(lián)公司于2013年開始,先為撫順縣等服務(wù)建站,撫順縣等地企業(yè),進行企業(yè)商務(wù)咨詢服務(wù)。為撫順縣企業(yè)網(wǎng)站制作PC+手機+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。
眾所周知,Node.js 是單線程、異步非阻塞的程序語言,那如何充分利用多核 CPU 的優(yōu)勢呢?這就需要用到 child_process 模塊來創(chuàng)建子進程了,在 Node.js 中,有四種方法可以創(chuàng)建子進程:
exec
execFile
spawn
fork
上面四個方法都會返回 ChildProcess
實例(繼承自 EventEmitter
),該實例擁有三個標(biāo)準(zhǔn)的 stdio 流:
child.stdin
child.stdout
child.stderr
子進程生命周期內(nèi)可以注冊監(jiān)聽的事件有:
exit
:子進程結(jié)束時觸發(fā),參數(shù)為 code 錯誤碼和 signal 中斷信號。
close
:子進程結(jié)束并且 stdio 流被關(guān)閉時觸發(fā),參數(shù)同 exit
事件。
disconnect
:父進程調(diào)用 child.disconnect()
或子進程調(diào)用 process.disconnect()
時觸發(fā)。
error
:子進程無法創(chuàng)建、或無法被殺掉、或發(fā)消息給子進程失敗時觸發(fā)。
message
:子進程通過 process.send()
發(fā)送消息時觸發(fā)。
spawn
:子進程創(chuàng)建成功時觸發(fā)(Node.js v15.1版本才添加此事件)。
而 exec
和 execFile
方法還額外提供了一個回調(diào)函數(shù),會在子進程終止的時候觸發(fā)。接下來進行詳細(xì)分析:
exec 方法用于執(zhí)行 bash 命令,它的參數(shù)是一個命令字符串。例如統(tǒng)計當(dāng)前目錄下的文件數(shù)量,用 exec 函數(shù)的寫法為:
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 會新建一個子進程,然后緩存它的運行結(jié)果,運行結(jié)束后調(diào)用回調(diào)函數(shù)。
可能你已經(jīng)想到了,exec 命令是比較危險的,假如把用戶提供的字符串作為 exec 函數(shù)的參數(shù),會面臨命令行注入的風(fēng)險,例如:
find . -type f | wc -l; rm -rf /;
另外,由于 exec 會在內(nèi)存中緩存全部的輸出結(jié)果,當(dāng)數(shù)據(jù)比較大的時候,spawn 會是更好的選擇。
execFile 和 exec 的區(qū)別在于它并不會創(chuàng)建 shell,而是直接執(zhí)行命令,所以會更高效一點,例如:
const { execFile } = require("child_process") const child = execFile("node", ["--version"], (error, stdout, stderr) => { if (error) throw error console.log(stdout) })
由于沒有創(chuàng)建 shell,程序的參數(shù)作為數(shù)組傳入,因此具有較高的安全性。
spawn 函數(shù)和 execFile 類似,默認(rèn)不開啟 shell,但區(qū)別在于 execFile 會緩存命令行的輸出,然后把結(jié)果傳入回調(diào)函數(shù)中,而 spawn 則是以流的方式輸出,有了流,就能非常方便的對接輸入和輸出了,例如典型的 wc
命令:
const child = spawn("wc") process.stdin.pipe(child.stdin) child.stdout.on("data", data => { console.log(`child stdout:\n${data}`) })
此時就會從命令行 stdin 獲取輸入,當(dāng)用戶觸發(fā)回車 + ctrl D
時就開始執(zhí)行命令,并把結(jié)果從 stdout 輸出。
wc 是 Word Count 的縮寫,用于統(tǒng)計單詞數(shù),語法為:
wc [OPTION]... [FILE]...如果在終端上輸入 wc 命令并回車,這時候統(tǒng)計的是從鍵盤輸入終端中的字符,再次按回車鍵,然后按
Ctrl + D
會輸出統(tǒng)計的結(jié)果。
通過管道還可以組合復(fù)雜的命令,例如統(tǒng)計當(dāng)前目錄下的文件數(shù)量,在 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", // 指定執(zhí)行目錄 env: { ANSWER: 42 }, // 指定環(huán)境變量(默認(rèn)是 process.env) detached: true, // 作為獨立進程存在 })
fork 函數(shù)是 spawn 函數(shù)的變體,使用 fork 創(chuàng)建的子進程和父進程之間會自動創(chuàng)建一個通信通道,子進程的全局對象 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)
當(dāng)調(diào)用 fork("child.js")
的時候,實際上就是用 node 來執(zhí)行該文件中的代碼,相當(dāng)于 spawn('node', ['./child.js'])
。
fork 的一個典型的應(yīng)用場景如下:假如現(xiàn)在用 Node.js 創(chuàng)建一個 http 服務(wù),當(dāng)路由為 compute
的時候,執(zhí)行一個耗時的運算。
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 }
那么在上線后,只要服務(wù)端收到了 compute
請求,由于 Node.js 是單線程的,耗時運算占用了 CPU,用戶的其他請求都會阻塞在這里,表現(xiàn)出來的現(xiàn)象就是服務(wù)器無響應(yīng)。
解決這個問題最簡單的方法就是把耗時運算放到子進程中去處理,例如創(chuàng)建一個 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) })
再把服務(wù)端的代碼稍作改造:
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)
這樣的話,主線程就不會阻塞,而是繼續(xù)處理其他的請求,當(dāng)耗時運算的結(jié)果返回后,再做出響應(yīng)。其實更簡單的處理方式是利用 cluster 模塊,限于篇幅原因,后面再展開講。
掌握了上面四種創(chuàng)建子進程的方法之后,總結(jié)了以下三條規(guī)律:
創(chuàng)建 node 子進程用 fork,因為自帶通道方便通信。
創(chuàng)建非 node 子進程用 execFile 或 spawn。如果輸出內(nèi)容較少用 execFile,會緩存結(jié)果并傳給回調(diào)方便處理;如果輸出內(nèi)容多用 spawn,使用流的方式不會占用大量內(nèi)存。
執(zhí)行復(fù)雜的、固定的終端命令用 exec,寫起來更方便。但一定要記住 exec 會創(chuàng)建 shell,效率不如 execFile 和 spawn,且存在命令行注入的風(fēng)險。
感謝各位的閱讀,以上就是“Node.js中創(chuàng)建子進程的方法有哪些”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對Node.js中創(chuàng)建子進程的方法有哪些這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!