Nodejs子進(jìn)程、多進(jìn)程
我們都知道 Nodejs 是以單線程的模式運行的,但它使用的是事件驅(qū)動來處理并發(fā),這樣有助于我們在多核 cpu 的系統(tǒng)上創(chuàng)建多個子進(jìn)程,從而提高性能。
默認(rèn)情況下,Nodejs 的父進(jìn)程與衍生的子進(jìn)程之間會建立 stdin、stdout 和 stderr 的管道。 數(shù)據(jù)能以非阻塞的方式在管道中流通。
Node 提供了 child_process 模塊來創(chuàng)建子進(jìn)程,方法有:
child_process.exec(command[, options][, callback])使用子進(jìn)程執(zhí)行命令,緩存子進(jìn)程的輸出,并將子進(jìn)程的輸出以回調(diào)函數(shù)參數(shù)的形式返回。child_process.spawn(command[, args][, options])使用指定的命令行參數(shù)創(chuàng)建新進(jìn)程。child_process.fork(modulePath[, args][, options])是 spawn()的特殊形式,用于在子進(jìn)程中運行的模塊,如 fork('/haha.js') 相當(dāng)于 spawn('node', ['/haha.js']) 。與spawn方法不同的是,fork會在父進(jìn)程與子進(jìn)程之間,建立一個通信管道,用于進(jìn)程之間的通信。
每個函數(shù)都返回 ChildProcess 實例。 這些實例實現(xiàn)了 Node.js EventEmitter API,允許父進(jìn)程注冊監(jiān)聽器函數(shù),在子進(jìn)程生命周期期間,當(dāng)特定的事件發(fā)生時會調(diào)用這些函數(shù)。
下面重點介紹spawn和fork方法。
spawn()方法
child_process.spawn(command[, args][, options])
參數(shù)說明如下:
- command:將要運行的命令,就是cmd窗口中輸入的命令(windows下)
- args:Array字符串參數(shù)數(shù)組
- options Object:帶有下列參數(shù)
cwd String 子進(jìn)程當(dāng)前工作目錄,默認(rèn)繼承父進(jìn)程的工作目錄。注意:__dirname也就是當(dāng)前模塊文件夾目錄跟process.cwd()也就是current working directory當(dāng)前工作目錄可不是一個東西哦!__dirname其實跟progress.execPath是一樣的。
env Object 環(huán)境變量鍵值對,這個沒啥好說的就是當(dāng)用當(dāng)前路徑找不到的時候就去環(huán)境變量里找,定義環(huán)境變量就是方便直接調(diào)用一些東西。
stdio Array|String 子進(jìn)程stdio配置,默認(rèn)都是pipe,子進(jìn)程創(chuàng)建完成后會在返回的ChildProcess實例中攜帶(CP實例是父進(jìn)程里的)子進(jìn)程的3個標(biāo)準(zhǔn)流的引用,如果設(shè)置為inherit則會繼承父進(jìn)程的輸入輸出流,這時候你在子進(jìn)程里也可以打印父進(jìn)程的console.log(),只需要對在子進(jìn)程代碼中對process(代表子進(jìn)程本身)進(jìn)行process.stdout.on('data')的事件監(jiān)聽。
detached boolean 準(zhǔn)備將子進(jìn)程獨立于父進(jìn)程運行
uid number 設(shè)置該進(jìn)程的用戶標(biāo)識
gid number 設(shè)置該進(jìn)程的組標(biāo)識
代碼實例:
父進(jìn)程代碼:
const child_process = require('child_process');
console.log('當(dāng)前工作目錄: ', process.cwd())
console.log('當(dāng)前模塊文件目錄: ', __dirname)
for(var i = 0; i < 3; i++) {
var cp = child_process.spawn('node', [__dirname + '/haha.js', i]);
cp.stdout.on('data', function(data) {
console.log('stdout: ' + data);
});
cp.on('close', function(code) {
console.log('子進(jìn)程已退出,退出碼 ' + code);
});
}
子進(jìn)程代碼(haha.js):
console.log("進(jìn)程 " + process.argv[2] + "執(zhí)行" );
//process為全局變量,指的就是進(jìn)程本身

從上圖可見,創(chuàng)建子進(jìn)程也是一個異步的過程,并沒有按照代碼來順序執(zhí)行,開辟進(jìn)程0、1、2
延伸:process.argv
process.argv 屬性返回一個數(shù)組,這個數(shù)組包含了啟動Node.js進(jìn)程時的命令行參數(shù)。
argv[0]為[process.execPath]其實就是__dirname
argv[1]為__filename
argv[2~]為運行子進(jìn)程時為其傳入的其他參數(shù)。
fork()方法
child_process.fork() 方法是 child_process.spawn()的一個特殊情況,專門用于衍生新的 Node.js 進(jìn)程(spawn可以不用來運行JavaScript,比如運行一個telnet命令)。 跟 child_process.spawn()一樣返回一個 ChildProcess 實例。
但是,其返回的 ChildProcess會有一個額外的內(nèi)置的通信通道,它允許消息在父進(jìn)程和子進(jìn)程之間來回傳遞。有了它就可以不用監(jiān)聽標(biāo)準(zhǔn)流來獲取進(jìn)程間信息,可以直接通過ChildProcess/process.on('message',callback)這種方式獲取信息,并用ChildProcess/process.send(message[, callback])來進(jìn)行進(jìn)程間消息的發(fā)送。
注意: 消息是一個已解析的 JSON 對象或原始值(正常的字符串還是字符串),通過序列化和解析傳遞,結(jié)果就是消息可能跟開始發(fā)送的不完全一樣。
代碼實例:使用fork將一個socket服務(wù)器傳遞給子進(jìn)程運行
父進(jìn)程代碼:
const child_process = require('child_process');
var cp = child_process.fork(__dirname + '/haha.js')
const net = require('net');
var server = net.createServer(); //創(chuàng)建了一個socket服務(wù)器
server.on('connection', (socket) => {
//一旦建立連接,客戶端和服務(wù)器變成身份對稱的socket
socket.on('data', (data) => {
//服務(wù)器端socket向客戶端發(fā)送數(shù)據(jù)
//注意不要用end,end會結(jié)束連接的
socket.write('父進(jìn)程處理了你的消息')
})
})
server.listen(233, () => {
//將server傳遞給子進(jìn)程,讓它去處理
cp.send('server', server);
})
cp.on('message', (message) => {
console.log(message)
})
子進(jìn)程代碼:
process.on('message', (message, server) => {
if(message == 'server') {
server.on('connection', (socket) => {
socket.on('data', (data) => {
//向父進(jìn)程發(fā)送客戶端的消息
process.send(data.toString())
socket.write('子進(jìn)程處理了你的消息')
})
})
}
})
客戶端代碼:
const net = require('net');
var client = net.createConnection(233, () => {
setInterval(() => {
//一旦建立連接,則開始每500ms發(fā)送一次時間
client.write(new Date().toLocaleString())
}, 500)
})
client.on('data', (data) => {
console.log(data.toString())
})

其實按道理來說應(yīng)該是父進(jìn)程和子進(jìn)程共享了服務(wù)器端socket,他們會隨機(jī)的處理到來的數(shù)據(jù),但是我的代碼運行了很久,一直都是子進(jìn)程處理,可能是哪里出了問題吧。
今天就到這吧~