深入淺析Node.js中建立子進程的方法

青灯夜游
發布: 2021-10-12 10:05:48
轉載
2887 人瀏覽過

這篇文章帶大家了解一下Node.js中的子進程,介紹Node.js中創建子進程的四種方法,希望對大家有幫助!

深入淺析Node.js中建立子進程的方法

眾所周知,Node.js 是單執行緒、非同步非阻塞的程式語言,那該如何充分利用多核心 CPU 的優勢呢?這就需要用到child_process模組來建立子進程了,在Node.js 中,有四種方法可以建立子進程:

  • ##exec

  • execFile

  • #spawn

  • ##fork

  • 【推薦學習:《
nodejs 教學

》】上面四個方法都會回傳

ChildProcess

實例(繼承自EventEmitter),此實例有三個標準的 stdio 串流:

    ##child.stdin
  • child.stdout
  • fchild.stderr
  • 子程序生命週期內可以註冊監聽的事件有:

exit

:子程序結束時觸發,參數為code 錯誤碼和signal 中斷訊號。

close

:子程序結束並且 stdio 流被關閉時觸發,參數同

exit事件。disconnect

:父程序呼叫

child.disconnect()或子程序呼叫process.disconnect()時觸發。error

:子進程無法建立、或無法被殺、或發送訊息給子進程失敗時觸發。

message

:子進程透過

process.send()發送訊息時觸發。spawn

:當子程序建立成功時觸發(Node.js v15.1版本才加入此事件)。

exec

execFile方法也額外提供了一個回呼函數,會在子程序終止的時候觸發。接下來進行詳細分析: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}`) })
登入後複製

exec 會新建一個子進程,然後快取它的運作結果,執行結束後呼叫回呼函數。

可能你已經想到了,exec 指令是比較危險的,假如把使用者提供的字串當作exec 函數的參數,會面臨指令列注入的風險,例如:

find . -type f | wc -l; rm -rf /;
登入後複製

另外,由於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) })
登入後複製

由於沒有建立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}`) })
登入後複製
此時就會從命令列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

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

的時候,執行一個耗時的運算。

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

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

以上是深入淺析Node.js中建立子進程的方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:juejin.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!