在nodejs中建立child processnode
nodejs的main event loop是單線程的,nodejs自己也維護着Worker Pool用來處理一些耗時的操做,咱們還能夠經過使用nodejs提供的worker_threads來手動建立新的線程來執行本身的任務。shell
本文將會介紹一種新的執行nodejs任務的方式,child process。windows
lib/child_process.js提供了child_process模塊,經過child_process咱們能夠建立子進程。服務器
注意,worker_threads建立的是子線程,而child_process建立的是子進程。併發
在child_process模塊中,能夠同步建立進程也能夠異步建立進程。同步建立方式只是在異步建立的方法後面加上Sync。異步
建立出來的進程用ChildProcess類來表示。socket
咱們看下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, 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。oop
ChildProcess能夠接收到event有5種,分別是close,disconnect,error,exit和message。ui
當調用父進程中的 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, // stdin Readable | null, // stdout Readable | null, // stderr Readable | Writable | null | undefined, // 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', (m, 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[, args][, options]) child_process.exec(command[, options][, callback]) child_process.execFile(file[, args][, options][, 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, stdout, 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', (err, stdout, stderr) => { // ... });
同步建立進程可使用child_process.spawnSync()、child_process.execSync() 和 child_process.execFileSync() ,同步的方法會阻塞 Node.js 事件循環、暫停任何其餘代碼的執行,直到子進程退出。
一般對於一些腳本任務來講,使用同步建立進程會比較經常使用。
本文做者:flydean程序那些事
本文連接:http://www.flydean.com/nodejs-childprocess/
本文來源:flydean的博客
歡迎關注個人公衆號:「程序那些事」最通俗的解讀,最深入的乾貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!