>>>【說明】還是一如既往的,這篇文章是從我的個人博客裏挪過來的。原文參見:http://www.jscon.co/coding/frontend/nodejs_fork_child_process.html
Child Process模塊還提供一個用於創建一個也是Node.js的子進程,並提供父子進程具備通信通道的能力,這個方法稱爲 fork() ,相當於spawn('node', ['./child.js'])。與默認的spawn不同的是,fork會在父進程與子進程直接建立一個 IPC管道 ,用於父子進程之間的通信。
使用 fork() 的開銷在於每個子進程是個全新的V8實例,Node.js文檔提到,每一個進程需要花費30ms 啓動並佔用 10MB 內存,所以能啓動多少個子進程取決於計算機有多少內存。
1、一個例子
【舉例】現在我們模擬Boss向職員分配任務的過程。情景是這樣的:Boss現在有三個任務讓三個職員去做,一個負責一個。這三個職員不能並行處理這三個任務,只有當完成一個任務後,Boss纔會分配給另外一個職員下一個任務。
(苦逼碼農:我的Boss向來讓我一個人頂仨~~)
這裏的Boss是父線程,職員是子線程。我們一步一步來完成這個情節的代碼。
【注意】
fork函數有一個問題,就是它只能運行JavaScript代碼,如果你喜歡用CoffeeScript(或者其他任何編譯到js的語言),是無法通過fork調用的。
文章《Node.js中的child_process及進程通信》提供了一種方法,不過我沒有成功,大家可以試試:
一個簡單的方法是把代碼編譯到JavaScript再運行,但是很不方便,有沒有什麼辦法呢?
答案是可以的,還是得回到spawn函數。spawn函數除了接受command, args外,還接受一個options參數。通過把options參數的stdio設爲['ipc'],即可在父子進程之間建立IPC管道。例如子進程使用CoffeeScript:
child_process = require('child_process') options = stdio: ['ipc'] child = child_process.spawn 'coffee', ['./child.coffee'], options
因此下面的程序雖是用CoffeeScript編寫的,但最後都編譯成js文件再運行。
2、創建基礎父子進程
CoffeeScript代碼:
父進程所在文件:(removed.coffee)
fork = require("child_process").fork # 引入fork組件 child = fork "child.js" # 生成child子進程 console.log "parent pid:",process.pid child.on "message",(m)-> #定義父進程收到子進程發來消息時的動作 console.log "receive message:",m setTimeout ()-> child.kill "SIGINT" #子進程完成任務了,過3秒後殺死child進程 ,3000 child.on 'exit',(code,sig)-> #當子進程退出時所觸發的動作 console.log code,":",sig child.send "task1" # OK,開始向子進程發送消息!
子進程所在文件:(child.coffee)
console.log "===========================" console.log "child pid:",process.pid process.on 'message',(m)-> #定義子進程收到父進程發來消息時的動作 console.log process.pid,'start new task:',m console.log "start process.....Done" console.log "communiate with parent...." process.send "done" #子進程完成任務後,向父進程彙報
上面的代碼大部分是裝飾形的log代碼,其中有註釋的代碼纔是關鍵的。
代碼解釋:
上面的代碼講了這麼一個故事:Boss通過fork生成一個子進程,給子進程喊了一句“task1”後,子進程就屁顛屁顛得去做任務。子進程完成後向父進程彙報“done”,父進程得知職員辦事給力,一高興,3秒鐘之後kill了這個進程,然後,就沒有然後了….
(這是在拍《風雲》的節奏啊…..)
運行結果:
3、完成進程管理
上面的代碼只生成了一個子進程,說好的三個呢?有了上面的基礎代碼,完成題目的要求就比較簡單了,這次我們只稍微修改父進程所在的腳本即可:
CoffeeScript代碼:
父進程所在文件:(removed.coffee)
fork = require("child_process").fork child = fork "child.js" console.log "parent pid:",process.pid rebuild = (child)-> # 將兩個事件偵聽器包裝秤rebuild函數 child.on "message",(m)-> console.log "receive message:",m setTimeout ()-> child.kill "SIGINT" # kill 子進程 child = fork "child.js" # 重生成子進程 rebuild(child) # 給生成的子進程綁定偵聽器 child.send "task1" # 父進程給子進程發消息 ,3000 child.on 'exit',(code,sig)-> console.log code,":",sig rebuild(child) child.send "task1"
代碼解釋:
這是“稍微修改”的意思?果然呵,男人都是騙子!
通過現象看本質,看一下修改的步驟:
① 這裏把前一部分的事件偵聽器代碼(就是那兩個 on 什麼的啦)包裝了一下,命名成一個 rebuild函數 。這裏的重點是然後再在遞歸使用。因爲我們在每一次任務完成後會kill掉子進程,所以被kill掉的子進程的偵聽器自然跟着消亡,所以每次fork之後就需要重新綁定事件(就是那兩個 on 什麼的啦):
child.kill "SIGINT" child = fork "child.js" rebuild(child) child.send "task"+count
也許通過這四行代碼你就會發現,俺家之所以將該函數取名爲rebuild函數是有原因的:鳳凰涅槃,浴火重生;第一句kill上次的child,接着fork一個新的child,同時rebuild出該新函數的事件偵聽器(就是那兩個 on 什麼的啦),接着父元素有向這個新生的child發出“task1”信號….
② 猜想的出來執行之後的效果麼?如果認爲會出現刷屏代碼的話,說明你已經懂了
運行結果:
很明顯上面的代碼不是我們所想要的,我們只要三個子進程即可!好吧,杜絕超生,那隻能計劃生育了。加個count變量,加個 if… else… 語句就行了。不多說,翠花,上代碼:
CoffeeScript代碼:
父進程所在文件:(removed.js)
fork = require("child_process").fork child = fork "child.js" console.log "parent pid:",process.pid count = 0 rebuild = (child)-> child.on "message",(m)-> count += 1 if count < 3 console.log "receive message:",m setTimeout ()-> child.kill "SIGINT" child = fork "child.js" rebuild(child) child.send "task"+count ,3000 else console.log "receive message:",m console.log "task over" setTimeout ()-> child.kill "SIGINT" ,3000 child.on 'exit',(code,sig)-> console.log code,":",sig rebuild(child) child.send "task"+count
運行結果:
[caption id="" align="aligncenter" width="364"] 完成有限的進程管理[/caption]
4、傳遞參數
如果僅僅這樣單純的管理的話,父子進程(爲啥我總打成“父子進城”呢??)其實不過爾爾,花拳繡腿罷了。一般的情形父進程往往會附帶一些詳細的指令給子進程,比如“去城裏買些菜回來”~~
傳遞的message可以是JSON格式,真的是JSON,不騙你,不帶解析的!
CoffeeScript代碼:
父進程所在文件 removed.coffee 修改的地方較少,只有兩處但很關鍵:
child.send "task"+count
改成:
child.send {where:"城市"+count,fn:"buy"}
沒錯,傳送的就是JSON格式的數據。
子進程所在文件:(child.coffee)
console.log "===========================" console.log "child pid:",process.pid exports.buy = ()-> "買菜" #定義buy函數 process.on 'message',(m)-> console.log process.pid,'start new task:',m console.log "到",m.where,",",exports[m.fn]() #字符串和函數 console.log "start process.....Done" console.log "communiate with parent...." process.send "done"
這裏我想表達的意思有兩個:
① 父子線程間的通信可以是JSON格式的,不用解析可以直接使用;
② 你看出來了麼,“buy”是父進程傳給子進程的一個字符串量,但在子進程中調用了其buy函數~~
運行結果:
[caption id="" align="aligncenter" width="454"] 進程通信中的傳遞參數[/caption]故事的結尾,爲了響應Boss的號召,這三個職員就真的去三個城市買菜,賣完菜後,Boss很十分感動, 就kill 它們了,然後,就沒有然後了….
(汗…………………………………………………………….. The End)