Node 雖然自身存在多個線程,但是運行在v8 上的JavaScript 是單線程的。 Node 的child_process 模塊用於創建子進程,我們可以通過子進程充分利用CPU。範例:
複製代碼代碼如下:
var fork = require('child_process').fork;
// 獲取當前機器的CPU 數量
var cpus = require('os').cpus();
for (var i = 0; i < cpus.length; i++) {
// 生成新進程
fork('./worker.js');
}
這裡了解一下包括fork 在內的幾個進程創建方法:
1.spawn(command, [args], [options]),啟動一個新進程來執行命令command,args 為命令行參數
2.exec(command, [options], callback),啟動一個新進程來執行命令command,callback 用於在進程結束時獲取標準輸入、標準輸出,以及錯誤信息
3.execFile(file, [args], [options], [callback]),啟動一個新進程來執行可執行文件file,callback 用於在進程結束時獲取標準輸入、標準輸出,以及錯誤信息
4.fork(modulePath, [args], [options]),啟動一個新進程來執行一個JavaScript 文件模塊,這時候創建的是Node 子進程
Node 進程間通信
父進程
複製代碼代碼如下:
// parent.js
var fork = require('child_process').fork;
// fork 返回子進程對象n
var n = fork('./child.js');
// 處理事件message
n.on('message', function(m) {
// 收到子進程發送的消息
console.log('got message: ' + m);
});
// 向子進程發送消息
n.send({hello: 'world'});
子進程
複製代碼代碼如下:
// child.js
// 處理事件message
process.on('message', function(m) {
console.log('got message: ' + m);
});
// process 存在send 方法,用於向父進程發送消息
process.send({foo: 'bar'});
需要注意的是,這裡的send 方法是同步的,因此不建議用於發送大量的數據(可以使用pipe 來代替,詳細見:http://nodejs.org/api/all.html#child_process_child_process_spawn_command_args_options)。
特殊的情況,消息中cmd 屬性值包含NODE_ 前綴(例如:{cmd: 'NODE_foo'} 消息),那麼此消息不會被提交到message 事件(而是internalMessage 事件),它們被Node 內部使用。
send 方法的原型為:
複製代碼代碼如下:
send(message, [sendHandle])
這裡,sendHandle(handle)可以被用於發送:
1.net.Native,原生的C++ TCP socket 或者管道
2.net.Server,TCP 服務器
3.net.Socket,TCP socket
4.dgram.Native,原生的C++ UDP socket
5.dgram.Socket,UDP socket
send 發送sendHandle 時實際上不是(也不能)直接發送JavaScript 對象,而是發送文件描述符(最終以JSON 字符串發送),其他進程能夠通過這個文件描述符還原出對應對象。
現在看一個例子:
父進程
複製代碼代碼如下:
// parent.js
var fork = require('child_process').fork;
var n = fork('./child.js');
var server = require('net').createServer();
server.listen(7000, function() {
// 發送TCP server 到子進程
n.send('server', server);
}).on('connection', function() {
console.log('connection - parent');
});
子進程
複製代碼代碼如下:
process.on('message', function(m, h) {
if (m === 'server') {
h.on('connection', function() {
console.log('connection - child');
});
}
});
通過端口7000 訪問此程序,得到輸出可能為connection parent 也可能得到輸出connection child。這裡子進程和父進程同時監聽了端口7000。通常來說,多個進程監聽同一個端口會引起EADDRINUSE 的異常,而此例的情況是,不同的兩個進程使用了相同的文件描述符,且Node 底層在監聽端口時對socket 設置了SO_REUSEADDR 選項,這使得此socket 可以在不同的進程間復用。在多個進程監聽同一個端口時,同一時刻文件描述符只能被一個進程使用,這些進程對socket 的使用是搶占式的。
cluster 模塊
在Node 的v0.8 新增了cluster 模塊,通過cluster 模塊能夠輕鬆的在一台物理機器上構建一組監聽相同端口的進程。範例:
複製代碼代碼如下:
var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;
// 檢查進程是否是master 進程
if (cluster.isMaster) {
for (var i = 0; i < numCPUs; ++i)
// 生成新的worker 進程(只有master 進程才可以調用)
cluster.fork();
cluster.on('exit', function(worker, code, signal) {
console.log('worker ' + worker.process.pid + ' died');
});
} else {
http.createServer(function(req, res) {
res.writeHead(200);
res.end('hello world/n');
}).listen(8000);
}
我們在worker 進程中調用listen 方法,監聽請求將會傳遞給master 進程。如果master 進程已經存在一個正在監聽的server 符合worker 進程的要求,那麼此server 的handle 將會傳遞給worker,如果不存在,master 進程則會創建一個,然後將handle 傳遞給worker 進程。
更多詳細的關於cluster 的文檔:http://www.nodejs.org/api/cluster.html