1. 程式人生 > 程式設計 >在nodejs中建立child process的方法

在nodejs中建立child process的方法

簡介

nodejs的main event loop是單執行緒的,nodejs本身也維護著Worker Pool用來處理一些耗時的操作,我們還可以通過使用nodejs提供的worker_threads來手動建立新的執行緒來執行自己的任務。

本文將會介紹一種新的執行nodejs任務的方式,child process。

child process

lib/child_process.js提供了child_process模組,通過child_process我們可以建立子程序。

注意,worker_threads建立的是子執行緒,而child_process建立的是子程序。

在child_process模組中,可以同步建立程序也可以非同步建立程序。同步建立方式只是在非同步建立的方法後面加上Sync。

創建出來的程序用ChildProcess類來表示。

我們看下ChildProcess的定義:

interface ChildProcess extends events.EventEmitter {
 stdin: Writable | null;
 stdout: Readable | null;
 stderr: Readable | null;
 readonly channel?: Pipe | null;
 readonly stdio: [
  Writable | null,// stdin
  Readable | null,// stdout
  Readable | null,// stderr
  Readable | Writable | null | undefined,// extra
  Readable | Writable | null | undefined // extra
 ];
 readonly killed: boolean;
 readonly pid: number;
 readonly connected: boolean;
 readonly exitCode: number | null;
 readonly signalCode: NodeJS.Signals | null;
 readonly spawnargs: string[];
 readonly spawnfile: string;
 kill(signal?: NodeJS.Signals | number): boolean;
 send(message: Serializable,callback?: (error: Error | null) => void): boolean;
 send(message: Serializable,sendHandle?: SendHandle,options?: MessageOptions,callback?: (error: Error | null) => void): boolean;
 disconnect(): void;
 unref(): void;
 ref(): void;

 /**
  * events.EventEmitter
  * 1. close
  * 2. disconnect
  * 3. error
  * 4. exit
  * 5. message
  */
 ...
 }

可以看到ChildProcess也是一個EventEmitter,所以它可以傳送和接受event。

ChildProcess可以接收到event有5種,分別是close,disconnect,error,exit和message。

當呼叫父程序中的 subprocess.disconnect() 或子程序中的 process.disconnect() 後會觸發 disconnect 事件。

當出現無法建立程序,無法kill程序和向子程序傳送訊息失敗的時候都會觸發error事件。

當子程序結束後時會觸發exit事件。

當子程序的 stdio 流被關閉時會觸發 close 事件。 注意,close事件和exit事件是不同的,因為多個程序可能共享同一個stdio,所以傳送exit事件並不一定會觸發close事件。

看一個close和exit的例子:

const { spawn } = require('child_process');
const ls = spawn('ls',['-lh','/usr']);

ls.stdout.on('data',(data) => {
 console.log(`stdout: ${data}`);
});

ls.on('close',(code) => {
 console.log(`子程序使用程式碼 $[code] 關閉所有 stdio`);
});

ls.on('exit',(code) => {
 console.log(`子程序使用程式碼 $[code] 退出`);
});

最後是message事件,當子程序使用process.send() 傳送訊息的時候就會被觸發。

ChildProcess中有幾個標準流屬性,分別是stderr,stdout,stdin和stdio。

stderr,stdout,stdin很好理解,分別是標準錯誤,標準輸出和標準輸入。

我們看一個stdout的使用:

const { spawn } = require('child_process');

const subprocess = spawn('ls');

subprocess.stdout.on('data',(data) => {
 console.log(`接收到資料塊 ${data}`);
});

stdio實際上是stderr,stdout,stdin的集合:

readonly stdio: [
  Writable | null,// extra
  Readable | Writable | null | undefined // extra
 ];

其中stdio[0]表示的是stdin,stdio[1]表示的是stdout,stdio[2]表示的是stderr。

如果在通過stdio建立子程序的時候,這三個標準流被設定為除pipe之外的其他值,那麼stdin,stdout和stderr將為null。

我們看一個使用stdio的例子:

const assert = require('assert');
const fs = require('fs');
const child_process = require('child_process');

const subprocess = child_process.spawn('ls',{
 stdio: [
 0,// 使用父程序的 stdin 用於子程序。
 'pipe',// 把子程序的 stdout 通過管道傳到父程序 。
 fs.openSync('err.out','w') // 把子程序的 stderr 定向到一個檔案。
 ]
});

assert.strictEqual(subprocess.stdio[0],null);
assert.strictEqual(subprocess.stdio[0],subprocess.stdin);

assert(subprocess.stdout);
assert.strictEqual(subprocess.stdio[1],subprocess.stdout);

assert.strictEqual(subprocess.stdio[2],null);
assert.strictEqual(subprocess.stdio[2],subprocess.stderr);

通常情況下父程序中維護了一個對子程序的引用計數,只有在當子程序退出之後父程序才會退出。

這個引用就是ref,如果呼叫了unref方法,則允許父程序獨立於子程序退出。

const { spawn } = require('child_process');

const subprocess = spawn(process.argv[0],['child_program.js'],{
 detached: true,stdio: 'ignore'
});

subprocess.unref();

最後,我們看一下如何通過ChildProcess來發送訊息:

subprocess.send(message[,sendHandle[,options]][,callback])

其中message就是要傳送的訊息,callback是傳送訊息之後的回撥。

sendHandle比較特殊,它可以是一個TCP伺服器或socket物件,通過將這些handle傳遞給子程序。子程序將會在message事件中,將該handle傳遞給Callback函式,從而可以在子程序中進行處理。

我們看一個傳遞TCP server的例子,首先看主程序:

const subprocess = require('child_process').fork('subprocess.js');

// 開啟 server 物件,併發送該控制代碼。
const server = require('net').createServer();
server.on('connection',(socket) => {
 socket.end('由父程序處理');
});
server.listen(1337,() => {
 subprocess.send('server',server);
});

再看子程序:

process.on('message',(m,server) => {
 if (m === 'server') {
 server.on('connection',(socket) => {
 socket.end('由子程序處理');
 });
 }
});

可以看到子程序接收到了server handle,並且在子程序中監聽connection事件。

下面我們看一個傳遞socket物件的例子:

onst { fork } = require('child_process');
const normal = fork('subprocess.js',['normal']);
const special = fork('subprocess.js',['special']);

// 開啟 server,併發送 socket 給子程序。
// 使用 `pauseOnConnect` 防止 socket 在被髮送到子程序之前被讀取。
const server = require('net').createServer({ pauseOnConnect: true });
server.on('connection',(socket) => {

 // 特殊優先順序。
 if (socket.remoteAddress === '74.125.127.100') {
 special.send('socket',socket);
 return;
 }
 // 普通優先順序。
 normal.send('socket',socket);
});
server.listen(1337);

subprocess.js的內容:

process.on('message',socket) => {
 if (m === 'socket') {
 if (socket) {
 // 檢查客戶端 socket 是否存在。
 // socket 在被髮送與被子程序接收這段時間內可被關閉。
 socket.end(`請求使用 ${process.argv[2]} 優先順序處理`);
 }
 }
});

主程序建立了兩個subprocess,一個處理特殊的優先順序, 一個處理普通的優先順序。

非同步建立程序

child_process模組有4種方式可以非同步建立程序,分別是child_process.spawn()、child_process.fork()、child_process.exec() 和 child_process.execFile()。

先看一個各個方法的定義:

child_process.spawn(command[,args][,options])

child_process.fork(modulePath[,options])

child_process.exec(command[,options][,callback])

child_process.execFile(file[,callback])

其中child_process.spawn是基礎,他會非同步的生成一個新的程序,其他的fork,exec和execFile都是基於spawn來生成的。

fork會生成新的Node.js 程序。

exec和execFile是以新的程序執行新的命令,並且帶有callback。他們的區別就在於在windows的環境中,如果要執行.bat或者.cmd檔案,沒有shell終端是執行不了的。這個時候就只能以exec來啟動。execFile是無法執行的。

或者也可以使用spawn。

我們看一個在windows中使用spawn和exec的例子:

// 僅在 Windows 上。
const { spawn } = require('child_process');
const bat = spawn('cmd.exe',['/c','my.bat']);

bat.stdout.on('data',(data) => {
 console.log(data.toString());
});

bat.stderr.on('data',(data) => {
 console.error(data.toString());
});

bat.on('exit',(code) => {
 console.log(`子程序退出,退出碼 $[code]`);
});
const { exec,spawn } = require('child_process');
exec('my.bat',(err,stderr) => {
 if (err) {
 console.error(err);
 return;
 }
 console.log(stdout);
});

// 檔名中包含空格的指令碼:
const bat = spawn('"my script.cmd"',['a','b'],{ shell: true });
// 或:
exec('"my script.cmd" a b',stderr) => {
 // ...
});

同步建立程序

同步建立程序可以使用child_process.spawnSync()、child_process.execSync() 和 child_process.execFileSync() ,同步的方法會阻塞 Node.js 事件迴圈、暫停任何其他程式碼的執行,直到子程序退出。

通常對於一些指令碼任務來說,使用同步建立程序會比較常用。

到此這篇關於在nodejs中建立child process的方法的文章就介紹到這了,更多相關nodejs中建立child process內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!