在nodejs中創(chuàng)建child process的方法
簡介
nodejs的main event loop是單線程的,nodejs本身也維護(hù)著Worker Pool用來處理一些耗時(shí)的操作,我們還可以通過使用nodejs提供的worker_threads來手動(dòng)創(chuàng)建新的線程來執(zhí)行自己的任務(wù)。
本文將會(huì)介紹一種新的執(zhí)行nodejs任務(wù)的方式,child process。
child process
lib/child_process.js提供了child_process模塊,通過child_process我們可以創(chuàng)建子進(jìn)程。
注意,worker_threads創(chuàng)建的是子線程,而child_process創(chuàng)建的是子進(jìn)程。
在child_process模塊中,可以同步創(chuàng)建進(jìn)程也可以異步創(chuàng)建進(jìn)程。同步創(chuàng)建方式只是在異步創(chuàng)建的方法后面加上Sync。
創(chuàng)建出來的進(jìn)程用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, 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也是一個(gè)EventEmitter,所以它可以發(fā)送和接受event。
ChildProcess可以接收到event有5種,分別是close,disconnect,error,exit和message。
當(dāng)調(diào)用父進(jìn)程中的 subprocess.disconnect() 或子進(jìn)程中的 process.disconnect() 后會(huì)觸發(fā) disconnect 事件。
當(dāng)出現(xiàn)無法創(chuàng)建進(jìn)程,無法kill進(jìn)程和向子進(jìn)程發(fā)送消息失敗的時(shí)候都會(huì)觸發(fā)error事件。
當(dāng)子進(jìn)程結(jié)束后時(shí)會(huì)觸發(fā)exit事件。
當(dāng)子進(jìn)程的 stdio 流被關(guān)閉時(shí)會(huì)觸發(fā) close 事件。 注意,close事件和exit事件是不同的,因?yàn)槎鄠€(gè)進(jìn)程可能共享同一個(gè)stdio,所以發(fā)送exit事件并不一定會(huì)觸發(fā)close事件。
看一個(gè)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(`子進(jìn)程使用代碼 $[code] 關(guān)閉所有 stdio`); }); ls.on('exit', (code) => { console.log(`子進(jìn)程使用代碼 $[code] 退出`); });
最后是message事件,當(dāng)子進(jìn)程使用process.send() 發(fā)送消息的時(shí)候就會(huì)被觸發(fā)。
ChildProcess中有幾個(gè)標(biāo)準(zhǔn)流屬性,分別是stderr,stdout,stdin和stdio。
stderr,stdout,stdin很好理解,分別是標(biāo)準(zhǔn)錯(cuò)誤,標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)輸入。
我們看一個(gè)stdout的使用:
const { spawn } = require('child_process'); const subprocess = spawn('ls'); subprocess.stdout.on('data', (data) => { console.log(`接收到數(shù)據(jù)塊 ${data}`); });
stdio實(shí)際上是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創(chuàng)建子進(jìn)程的時(shí)候,這三個(gè)標(biāo)準(zhǔn)流被設(shè)置為除pipe之外的其他值,那么stdin,stdout和stderr將為null。
我們看一個(gè)使用stdio的例子:
const assert = require('assert'); const fs = require('fs'); const child_process = require('child_process'); const subprocess = child_process.spawn('ls', { stdio: [ 0, // 使用父進(jìn)程的 stdin 用于子進(jìn)程。 'pipe', // 把子進(jìn)程的 stdout 通過管道傳到父進(jìn)程 。 fs.openSync('err.out', 'w') // 把子進(jìn)程的 stderr 定向到一個(gè)文件。 ] }); 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);
通常情況下父進(jìn)程中維護(hù)了一個(gè)對(duì)子進(jìn)程的引用計(jì)數(shù),只有在當(dāng)子進(jìn)程退出之后父進(jìn)程才會(huì)退出。
這個(gè)引用就是ref,如果調(diào)用了unref方法,則允許父進(jìn)程獨(dú)立于子進(jìn)程退出。
const { spawn } = require('child_process'); const subprocess = spawn(process.argv[0], ['child_program.js'], { detached: true, stdio: 'ignore' }); subprocess.unref();
最后,我們看一下如何通過ChildProcess來發(fā)送消息:
subprocess.send(message[, sendHandle[, options]][, callback])
其中message就是要發(fā)送的消息,callback是發(fā)送消息之后的回調(diào)。
sendHandle比較特殊,它可以是一個(gè)TCP服務(wù)器或socket對(duì)象,通過將這些handle傳遞給子進(jìn)程。子進(jìn)程將會(huì)在message事件中,將該handle傳遞給Callback函數(shù),從而可以在子進(jìn)程中進(jìn)行處理。
我們看一個(gè)傳遞TCP server的例子,首先看主進(jìn)程:
const subprocess = require('child_process').fork('subprocess.js'); // 打開 server 對(duì)象,并發(fā)送該句柄。 const server = require('net').createServer(); server.on('connection', (socket) => { socket.end('由父進(jìn)程處理'); }); server.listen(1337, () => { subprocess.send('server', server); });
再看子進(jìn)程:
process.on('message', (m, server) => { if (m === 'server') { server.on('connection', (socket) => { socket.end('由子進(jìn)程處理'); }); } });
可以看到子進(jìn)程接收到了server handle,并且在子進(jìn)程中監(jiān)聽connection事件。
下面我們看一個(gè)傳遞socket對(duì)象的例子:
onst { fork } = require('child_process'); const normal = fork('subprocess.js', ['normal']); const special = fork('subprocess.js', ['special']); // 開啟 server,并發(fā)送 socket 給子進(jìn)程。 // 使用 `pauseOnConnect` 防止 socket 在被發(fā)送到子進(jìn)程之前被讀取。 const server = require('net').createServer({ pauseOnConnect: true }); server.on('connection', (socket) => { // 特殊優(yōu)先級(jí)。 if (socket.remoteAddress === '74.125.127.100') { special.send('socket', socket); return; } // 普通優(yōu)先級(jí)。 normal.send('socket', socket); }); server.listen(1337);
subprocess.js的內(nèi)容:
process.on('message', (m, socket) => { if (m === 'socket') { if (socket) { // 檢查客戶端 socket 是否存在。 // socket 在被發(fā)送與被子進(jìn)程接收這段時(shí)間內(nèi)可被關(guān)閉。 socket.end(`請求使用 ${process.argv[2]} 優(yōu)先級(jí)處理`); } } });
主進(jìn)程創(chuàng)建了兩個(gè)subprocess,一個(gè)處理特殊的優(yōu)先級(jí), 一個(gè)處理普通的優(yōu)先級(jí)。
異步創(chuàng)建進(jìn)程
child_process模塊有4種方式可以異步創(chuàng)建進(jìn)程,分別是child_process.spawn()、child_process.fork()、child_process.exec() 和 child_process.execFile()。
先看一個(gè)各個(gè)方法的定義:
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是基礎(chǔ),他會(huì)異步的生成一個(gè)新的進(jìn)程,其他的fork,exec和execFile都是基于spawn來生成的。
fork會(huì)生成新的Node.js 進(jìn)程。
exec和execFile是以新的進(jìn)程執(zhí)行新的命令,并且?guī)в衏allback。他們的區(qū)別就在于在windows的環(huán)境中,如果要執(zhí)行.bat或者.cmd文件,沒有shell終端是執(zhí)行不了的。這個(gè)時(shí)候就只能以exec來啟動(dòng)。execFile是無法執(zhí)行的。
或者也可以使用spawn。
我們看一個(gè)在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(`子進(jìn)程退出,退出碼 $[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) => { // ... });
同步創(chuàng)建進(jìn)程
同步創(chuàng)建進(jìn)程可以使用child_process.spawnSync()、child_process.execSync() 和 child_process.execFileSync() ,同步的方法會(huì)阻塞 Node.js 事件循環(huán)、暫停任何其他代碼的執(zhí)行,直到子進(jìn)程退出。
通常對(duì)于一些腳本任務(wù)來說,使用同步創(chuàng)建進(jìn)程會(huì)比較常用。
到此這篇關(guān)于在nodejs中創(chuàng)建child process的方法的文章就介紹到這了,更多相關(guān)nodejs中創(chuàng)建child process內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Node.js和MongoDB實(shí)現(xiàn)簡單日志分析系統(tǒng)
這篇文章主要介紹了Node.js和MongoDB實(shí)現(xiàn)簡單日志分析系統(tǒng),本文給出了服務(wù)器端、客戶端、圖表生成、Shell自動(dòng)執(zhí)行等功能的實(shí)現(xiàn)代碼,需要的朋友可以參考下2015-04-04垃圾回收器的相關(guān)知識(shí)點(diǎn)總結(jié)
本文是小編在網(wǎng)絡(luò)上整理的關(guān)于垃圾回收器的相關(guān)知識(shí)點(diǎn),很多語言和程序都用的到,有興趣的可以學(xué)習(xí)下。2018-05-05