在實際項目中,如果遇到需要大計算量的操作,按需fork(分叉)其實不是一個好的選擇。
因為fork的子進程也是V8(NodeJS的核心引擎)的新實例,每創建一個新實例,需要約30毫秒啟動時間,和至少10MB的初始內存。
也就是說,創建進程是有代價的,你不能創建太多,也不能頻繁創建。那樣,達不到提高進程效率的目的。
那么,該如何高效優雅的使用子進程呢?工作池!
工作池!
合理的辦法是創建一個可用的工作池,在池中存放足夠多的進程,并可以隨時分配使用。
我們對上一節講的內容進行升級:當父進程發送一個任務給子進程時,子進程執行任務。并將結果向主進程反饋。
在父進程中,需要的代碼會是這樣的:
function doWork(job,cb){var child = cp.fork("./worker");//發送工作給子進程child.send(job);//希望子進程返回一個確切的消息child.once("message",function(result){cb(null,result);})
}
嗯...這樣講有些凌亂,這一章比較復雜,最好的辦法,還是寫一個完整的代碼,做為例子:

1、father.js,主進程
var http = require("http");
var makePool = require("./pooler");
var runJob = makePool("./worker");http.createServer(function(req,res){runJob("some dummy job",function(er,data){console.log("father callback get:",data);if(er){return res.end("get an error:"+er.message)}res.end("work pool");})}).listen(8000)
當有客端訪問時,觸發runjob,開始啟行工作。
2、worker.js
process.on("message",function(job){console.log("worker get msg:",job);for(var i=0;i<10;i++){console.log("worker send:",job,i);process.send("finish job:"+job+i);}})
收到father主進程發來的消息時,使用process.send()方法調用子進程,向工作池發出工作任務。
3、pool.js(工作池)
接收worker消息,用工作池完成操作,并反饋給主程序。
代碼中做了詳細的注釋 ,就不單獨對代碼做解析了:
var cp = require("child_process");
//獲取CPU數量,有幾個CPU就創建幾個子進程,這樣就可以最大化的利用機器性能
var cpus = require("os").cpus().length;//模塊導出函數
module.exports = function(workModule){//等待任務隊列,當工作任務被下發,但沒有閑工作進程時,放到此隊列var awaiting = [];//存放準備就緒的工作進程var readyPool = [];//當前的工作子進程數量(工作池的大小)var poolSize = 0;return function doWork(job,cb){//如果工作池數量已經最大,并且沒有準備就緒的工作子進程,也就是所有工作子進程都在工作中,那么:排隊等待if(!readyPool.length && poolSize >cpus){//壓入到等待隊列,等待后續處理return awaiting.push([dowork,job.cb]);}//取得一個可用的工作子進程,或fork(分叉)一個新的子進程(增加工作池的大小)var child = readyPool.length ? readyPool.shift() : (poolSize++, cp.fork(workModule));{//子進程是否完成回調的標記var cbTriggered = false;//初始階段,移除子進程上的監聽,確保每個子進程只擁有一次監聽child.removeAllListeners();//錯誤child.once("error",function(err){//未回調if(!cbTriggered){//回調返回為錯誤cb(err);//回調標識改為true:已回調cbTriggered = true;} //結束子進程child.kill();//這里不用操作工作池poolSize--,因為kill會觸發exit事件,在exit事件中操作工作池});//子進程退出了(不明原因的意外退出、被kill()等都觸發)child.once("exit",function(code,signal){//未回調if(!cbTriggered){//回調,返回信息cb(new Error("Child exited with code:"+code))}//工作池(正在工作的子進程數)大小減一poolSize --;//退出的子進程,是否在準備好的子進程數組中var childIdx = readyPool.indexOf(child);if(childIdx > -1){//從準備好的子進程數組中移除readyPool.splice(childIdx,1);}})//獲取父進程發來的消息child.on("message",function(msg){console.log("pool get msg:",msg);cb(null,msg);cbTriggered = true;readyPool.push(child);//如何等待區有內容,處理之if(awaiting.length){setImmediate.apply(null,awaiting.shift());}//向父進程發送消息}).send(job);}//child區域結束}
}
執行效果

圖中展示的是工作流程,可見此種方法可以達到我們的預期,工作池很OK。
對于實際編程中遇到的消耗比較大的情況,使用此種方法可以極大的提高效率,且本文已經將工作池寫成了模塊(pooler.js)
建議收藏,nodejs開發,在某個時候一定會遇到適合的場景的。