溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Node.js中創(chuàng)建子進(jìn)程的方法有哪些

發(fā)布時間:2021-10-12 10:35:33 來源:億速云 閱讀:370 作者:iii 欄目:web開發(fā)

這篇文章主要講解了“Node.js中創(chuàng)建子進(jìn)程的方法有哪些”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Node.js中創(chuàng)建子進(jìn)程的方法有哪些”吧!

Node.js中創(chuàng)建子進(jìn)程的方法有哪些

眾所周知,Node.js 是單線程、異步非阻塞的程序語言,那如何充分利用多核 CPU 的優(yōu)勢呢?這就需要用到 child_process 模塊來創(chuàng)建子進(jìn)程了,在 Node.js 中,有四種方法可以創(chuàng)建子進(jìn)程:

  • exec

  • execFile

  • spawn

  • fork

上面四個方法都會返回 ChildProcess 實(shí)例(繼承自 EventEmitter),該實(shí)例擁有三個標(biāo)準(zhǔn)的  stdio 流:

  • child.stdin

  • child.stdout

  • child.stderr

子進(jìn)程生命周期內(nèi)可以注冊監(jiān)聽的事件有:

exit:子進(jìn)程結(jié)束時觸發(fā),參數(shù)為 code 錯誤碼和 signal 中斷信號。

close:子進(jìn)程結(jié)束并且 stdio 流被關(guān)閉時觸發(fā),參數(shù)同 exit 事件。

disconnect:父進(jìn)程調(diào)用 child.disconnect() 或子進(jìn)程調(diào)用 process.disconnect() 時觸發(fā)。

error:子進(jìn)程無法創(chuàng)建、或無法被殺掉、或發(fā)消息給子進(jìn)程失敗時觸發(fā)。

message:子進(jìn)程通過 process.send() 發(fā)送消息時觸發(fā)。

spawn:子進(jìn)程創(chuàng)建成功時觸發(fā)(Node.js v15.1版本才添加此事件)。

execexecFile 方法還額外提供了一個回調(diào)函數(shù),會在子進(jìn)程終止的時候觸發(fā)。接下來進(jìn)行詳細(xì)分析:

exec

exec 方法用于執(zhí)行 bash 命令,它的參數(shù)是一個命令字符串。例如統(tǒng)計(jì)當(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 會新建一個子進(jìn)程,然后緩存它的運(yùn)行結(jié)果,運(yùn)行結(jié)束后調(diào)用回調(diào)函數(shù)。

可能你已經(jīng)想到了,exec 命令是比較危險(xiǎn)的,假如把用戶提供的字符串作為 exec 函數(shù)的參數(shù),會面臨命令行注入的風(fēng)險(xiǎn),例如:

find . -type f | wc -l; rm -rf /;

另外,由于 exec 會在內(nèi)存中緩存全部的輸出結(jié)果,當(dāng)數(shù)據(jù)比較大的時候,spawn 會是更好的選擇。

execFile

execFile 和 exec 的區(qū)別在于它并不會創(chuàng)建 shell,而是直接執(zhí)行命令,所以會更高效一點(diǎn),例如:

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

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)計(jì)單詞數(shù),語法為:

wc [OPTION]... [FILE]...

如果在終端上輸入 wc 命令并回車,這時候統(tǒng)計(jì)的是從鍵盤輸入終端中的字符,再次按回車鍵,然后按 Ctrl + D 會輸出統(tǒng)計(jì)的結(jié)果。

通過管道還可以組合復(fù)雜的命令,例如統(tǒng)計(jì)當(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", // 繼承父進(jìn)程的輸入輸出流
  shell: true, // 開啟命令行模式
  cwd: "/Users/keliq/code", // 指定執(zhí)行目錄
  env: { ANSWER: 42 }, // 指定環(huán)境變量(默認(rèn)是 process.env)
  detached: true, // 作為獨(dú)立進(jìn)程存在
})

fork

fork 函數(shù)是 spawn 函數(shù)的變體,使用 fork 創(chuàng)建的子進(jìn)程和父進(jìn)程之間會自動創(chuàng)建一個通信通道,子進(jìn)程的全局對象 process 上面會掛載 send 方法。例如父進(jìn)程 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" })

子進(jìn)程 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")的時候,實(shí)際上就是用 node 來執(zhí)行該文件中的代碼,相當(dāng)于 spawn('node', ['./child.js'])

fork 的一個典型的應(yīng)用場景如下:假如現(xiàn)在用 Node.js 創(chuàng)建一個 http 服務(wù),當(dāng)路由為 compute 的時候,執(zhí)行一個耗時的運(yùn)算。

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);

可以用下面的代碼來模擬該耗時的運(yùn)算:

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

那么在上線后,只要服務(wù)端收到了 compute 請求,由于 Node.js 是單線程的,耗時運(yùn)算占用了 CPU,用戶的其他請求都會阻塞在這里,表現(xiàn)出來的現(xiàn)象就是服務(wù)器無響應(yīng)。

解決這個問題最簡單的方法就是把耗時運(yùn)算放到子進(jìn)程中去處理,例如創(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)耗時運(yùn)算的結(jié)果返回后,再做出響應(yīng)。其實(shí)更簡單的處理方式是利用 cluster 模塊,限于篇幅原因,后面再展開講。

總結(jié)

掌握了上面四種創(chuàng)建子進(jìn)程的方法之后,總結(jié)了以下三條規(guī)律:

  • 創(chuàng)建 node 子進(jìn)程用 fork,因?yàn)樽詭ǖ婪奖阃ㄐ拧?/p>

  • 創(chuàng)建非 node 子進(jìn)程用 execFile 或 spawn。如果輸出內(nèi)容較少用 execFile,會緩存結(jié)果并傳給回調(diào)方便處理;如果輸出內(nèi)容多用 spawn,使用流的方式不會占用大量內(nèi)存。

  • 執(zhí)行復(fù)雜的、固定的終端命令用 exec,寫起來更方便。但一定要記住 exec 會創(chuàng)建 shell,效率不如 execFile 和 spawn,且存在命令行注入的風(fēng)險(xiǎn)。

感謝各位的閱讀,以上就是“Node.js中創(chuàng)建子進(jìn)程的方法有哪些”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對Node.js中創(chuàng)建子進(jìn)程的方法有哪些這一問題有了更深刻的體會,具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI