摘要:而且方式創建的子進程與父進程之間建立了通信管道,因此子進程和父進程之間可以通過的方式發送消息。與事件的回調函數有兩個參數和,代碼子進程最終的退出碼,如果子進程是由于接收到信號終止的話,會記錄子進程接受的值。
在介紹child_process模塊之前,先來看一個下面的代碼。
const http = require("http"); const longComputation = () => { let sum = 0; for (let i = 0; i < 1e10; i++) { sum += i; }; return sum; }; 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);
可以試一下使用上面的代碼啟動Node.js服務,然后打開兩個瀏覽器選項卡分別訪問/compute和/,可以發現node服務接收到/compute請求時會進行大量的數值計算,導致無法響應其他的請求(/)。
在Java語言中可以通過多線程的方式來解決上述的問題,但是Node.js在代碼執行的時候是單線程的,那么Node.js應該如何解決上面的問題呢?其實Node.js可以創建一個子進程執行密集的cpu計算任務(例如上面例子中的longComputation)來解決問題,而child_process模塊正是用來創建子進程的。
創建子進程的方式child_process提供了幾種創建子進程的方式
異步方式:spawn、exec、execFile、fork
同步方式:spawnSync、execSync、execFileSync
首先介紹一下spawn方法
child_process.spawn(command[, args][, options]) command: 要執行的指令 args: 傳遞參數 options: 配置項
const { spawn } = require("child_process"); const child = spawn("pwd");
pwd是shell的命令,用于獲取當前的目錄,上面的代碼執行完控制臺并沒有任何的信息輸出,這是為什么呢?
控制臺之所以不能看到輸出信息的原因是由于子進程有自己的stdio流(stdin、stdout、stderr),控制臺的輸出是與當前進程的stdio綁定的,因此如果希望看到輸出信息,可以通過在子進程的stdout 與當前進程的stdout之間建立管道實現
child.stdout.pipe(process.stdout);
也可以監聽事件的方式(子進程的stdio流都是實現了EventEmitter API的,所以可以添加事件監聽)
child.stdout.on("data", function(data) { process.stdout.write(data); });
在Node.js代碼里使用的console.log其實底層依賴的就是process.stdout
除了建立管道之外,還可以通過子進程和當前進程共用stdio的方式來實現
const { spawn } = require("child_process"); const child = spawn("pwd", { stdio: "inherit" });
stdio選項用于配置父進程和子進程之間建立的管道,由于stdio管道有三個(stdin, stdout, stderr)因此stdio的三個可能的值其實是數組的一種簡寫
pipe 相當于["pipe", "pipe", "pipe"](默認值)
ignore 相當于["ignore", "ignore", "ignore"]
inherit 相當于[process.stdin, process.stdout, process.stderr]
由于inherit方式使得子進程直接使用父進程的stdio,因此可以看到輸出
ignore用于忽略子進程的輸出(將/dev/null指定為子進程的文件描述符了),因此當ignore時child.stdout是null。
spawn默認情況下并不會創建子shell來執行命令,因此下面的代碼會報錯
const { spawn } = require("child_process"); const child = spawn("ls -l"); child.stdout.pipe(process.stdout); // 報錯 events.js:167 throw er; // Unhandled "error" event ^ Error: spawn ls -l ENOENT at Process.ChildProcess._handle.onexit (internal/child_process.js:229:19) at onErrorNT (internal/child_process.js:406:16) at process._tickCallback (internal/process/next_tick.js:63:19) at Function.Module.runMain (internal/modules/cjs/loader.js:746:11) at startup (internal/bootstrap/node.js:238:19) at bootstrapNodeJSCore (internal/bootstrap/node.js:572:3) Emitted "error" event at: at Process.ChildProcess._handle.onexit (internal/child_process.js:235:12) at onErrorNT (internal/child_process.js:406:16) [... lines matching original stack trace ...] at bootstrapNodeJSCore (internal/bootstrap/node.js:572:3)
如果需要傳遞參數的話,應該采用數組的方式傳入
const { spawn } = require("child_process"); const child = spawn("ls", ["-l"]); child.stdout.pipe(process.stdout);
如果要執行ls -l | wc -l命令的話可以采用創建兩個spawn命令的方式
const { spawn } = require("child_process"); const child = spawn("ls", ["-l"]); const child2 = spawn("wc", ["-l"]); child.stdout.pipe(child2.stdin); child2.stdout.pipe(process.stdout);
也可以使用exec
const { exec } = require("child_process"); exec("ls -l | wc -l", function(err, stdout, stderr) { console.log(stdout); });
由于exec會創建子shell,所以可以直接執行shell管道命令。spawn采用流的方式來輸出命令的執行結果,而exec也是將命令的執行結果緩存起來統一放在回調函數的參數里面,因此exec只適用于命令執行結果數據小的情況。
其實spawn也可以通過配置shell option的方式來創建子shell進而支持管道命令,如下所示
const { spawn, execFile } = require("child_process"); const child = spawn("ls -l | wc -l", { shell: true }); child.stdout.pipe(process.stdout);
配置項除了stdio、shell之外還有cwd、env、detached等常用的選項
cwd用于修改命令的執行目錄
const { spawn, execFile, fork } = require("child_process"); const child = spawn("ls -l | wc -l", { shell: true, cwd: "/usr" }); child.stdout.pipe(process.stdout);
env用于指定子進程的環境變量(如果不指定的話,默認獲取當前進程的環境變量)
const { spawn, execFile, fork } = require("child_process"); const child = spawn("echo $NODE_ENV", { shell: true, cwd: "/usr" }); child.stdout.pipe(process.stdout); NODE_ENV=randal node b.js // 輸出結果 randal
如果指定env的話就會覆蓋掉默認的環境變量,如下
const { spawn, execFile, fork } = require("child_process"); spawn("echo $NODE_TEST $NODE_ENV", { shell: true, stdio: "inherit", cwd: "/usr", env: { NODE_TEST: "randal-env" } }); NODE_ENV=randal node b.js // 輸出結果 randal
detached用于將子進程與父進程斷開連接
例如假設存在一個長時間運行的子進程
// timer.js while(true) { }
但是主進程并不需要長時間運行的話就可以用detached來斷開二者之間的連接
const { spawn, execFile, fork } = require("child_process"); const child = spawn("node", ["timer.js"], { detached: true, stdio: "ignore" }); child.unref();
當調用子進程的unref方法時,同時配置子進程的stdio為ignore時,父進程就可以獨立退出了
execFile與exec不同,execFile通常用于執行文件,而且并不會創建子shell環境
fork方法是spawn方法的一個特例,fork用于執行js文件創建Node.js子進程。而且fork方式創建的子進程與父進程之間建立了IPC通信管道,因此子進程和父進程之間可以通過send的方式發送消息。
注意:fork方式創建的子進程與父進程是完全獨立的,它擁有多帶帶的內存,多帶帶的V8實例,因此并不推薦創建很多的Node.js子進程
fork方式的父子進程之間的通信參照下面的例子
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);
node parent.js // 輸出結果 Message from parent: { hello: "world" } Message from child { counter: 0 } Message from child { counter: 1 } Message from child { counter: 2 } Message from child { counter: 3 } Message from child { counter: 4 } Message from child { counter: 5 } Message from child { counter: 6 }
回到本文初的那個問題,我們就可以將密集計算的邏輯放到多帶帶的js文件中,然后再通過fork的方式來計算,等計算完成時再通知主進程計算結果,這樣避免主進程繁忙的情況了。
compute.js
const longComputation = () => { let sum = 0; for (let i = 0; i < 1e10; i++) { sum += i; }; return sum; }; process.on("message", (msg) => { const sum = longComputation(); process.send(sum); });
index.js
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);監聽進程事件
通過前述幾種方式創建的子進程都實現了EventEmitter,因此可以針對進程進行事件監聽
常用的事件包括幾種:close、exit、error、message
close事件當子進程的stdio流關閉的時候才會觸發,并不是子進程exit的時候close事件就一定會觸發,因為多個子進程可以共用相同的stdio。
close與exit事件的回調函數有兩個參數code和signal,code代碼子進程最終的退出碼,如果子進程是由于接收到signal信號終止的話,signal會記錄子進程接受的signal值。
先看一個正常退出的例子
const { spawn, exec, execFile, fork } = require("child_process"); const child = exec("ls -l", { timeout: 300 }); child.on("exit", function(code, signal) { console.log(code); console.log(signal); }); // 輸出結果 0 null
再看一個因為接收到signal而終止的例子,應用之前的timer文件,使用exec執行的時候并指定timeout
const { spawn, exec, execFile, fork } = require("child_process"); const child = exec("node timer.js", { timeout: 300 }); child.on("exit", function(code, signal) { console.log(code); console.log(signal); }); // 輸出結果 null SIGTERM
注意:由于timeout超時的時候error事件并不會觸發,并且當error事件觸發時exit事件并不一定會被觸發
error事件的觸發條件有以下幾種:
無法創建進程
無法結束進程
給進程發送消息失敗
注意當代碼執行出錯的時候,error事件并不會觸發,exit事件會觸發,code為非0的異常退出碼
const { spawn, exec, execFile, fork } = require("child_process"); const child = exec("ls -l /usrs"); child.on("error", function(code, signal) { console.log(code); console.log(signal); }); child.on("exit", function(code, signal) { console.log("exit"); console.log(code); console.log(signal); }); // 輸出結果 exit 1 null
message事件適用于父子進程之間建立IPC通信管道的時候的信息傳遞,傳遞的過程中會經歷序列化與反序列化的步驟,因此最終接收到的并不一定與發送的數據相一致。
sub.js
process.send({ foo: "bar", baz: NaN });
const cp = require("child_process"); const n = cp.fork(`${__dirname}/sub.js`); n.on("message", (m) => { console.log("got message:", m); // got message: { foo: "bar", baz: null } });
關于message有一種特殊情況要注意,下面的message并不會被子進程接收到
const { fork } = require("child_process"); const forked = fork("child.js"); forked.send({ cmd: "NODE_foo", hello: "world" });
當發送的消息里面包含cmd屬性,并且屬性的值是以NODE_開頭的話,這樣的消息是提供給Node.js本身保留使用的,因此并不會發出message事件,而是會發出internalMessage事件,開發者應該避免這種類型的消息,并且應當避免監聽internalMessage事件。
message除了發送字符串、object之外還支持發送server對象和socket對象,正因為支持socket對象才可以做到多個Node.js進程監聽相同的端口號。
未完待續......
參考資料https://medium.freecodecamp.o...
https://nodejs.org/dist/lates...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/107877.html
摘要:默認情況下,會打印堆棧信息到然后退出進程。適用于父子進程之間發送消息,關于如何創建父子進程會放在模塊中進行。信號雖然也是用于請求終止進程,但是它與有所不同,進程可以選擇響應還是忽略此信號。 process存在于全局對象上,不需要使用require()加載即可使用,process模塊主要做兩方面的事情 讀:獲取進程信息(資源使用、運行環境、運行狀態) 寫:執行進程操作(監聽事件、調度任...
摘要:子進程使用反序列化消息字符串為消息對象。在調用這類方法時,遍歷列表中的實例發送內部消息,子進程列表中的對應項收到內部消息并處理返回,父進程中再結合返回結果和對應著這個類實例維護的信息,保證功能的正確性。 在 Node.js 中,當我們使用 child_process 模塊創建子進程后,會返回一個 ChildProcess 類的實例,通過調用 ChildProcess#send(mess...
摘要:在單核系統之上我們采用單進程單線程的模式來開發。由進程來管理所有的子進程,主進程不負責具體的任務處理,主要工作是負責調度和管理。模塊與模塊總結無論是模塊還是模塊,為了解決實例單線程運行,無法利用多核的問題而出現的。 前言 進程與線程是一個程序員的必知概念,面試經常被問及,但是一些文章內容只是講講理論知識,可能一些小伙伴并沒有真的理解,在實際開發中應用也比較少。本篇文章除了介紹概念,通過...
摘要:說明模塊是的原始模塊主要作用執行命令行命令該模塊的功能主要由函數提供區分和執行命令第一個參數是將要執行的命令,命令之間的參數使用空格分開第二個參數是回調函數,有三個參數回調中的第一個參數命令執行錯誤會有值,否則為回調中的第二個參數子進程 1.說明 child_process 模塊是 Node.js 的原始模塊: 主要作用:執行命令行命令 該模塊的功能主要由 child_process...
摘要:一旦替換已經完成,該模塊將被完全棄用。用作錯誤處理事件文件,由在標準功能上的簡單包裝器提供所有模塊都提供這些對象。 Node.js簡介 Node 定義 Node.js是一個建立在Chrome v8 引擎上的javascript運行時環境 Node 特點 異步事件驅動 showImg(https://segmentfault.com/img/bVMLD1?w=600&h=237); no...
閱讀 791·2021-09-22 16:01
閱讀 2083·2021-08-20 09:37
閱讀 1693·2019-08-30 15:54
閱讀 1689·2019-08-30 15:44
閱讀 826·2019-08-28 18:23
閱讀 3005·2019-08-26 12:17
閱讀 1005·2019-08-26 11:56
閱讀 1539·2019-08-23 16:20