【NodeJS線程】Boss和他的職員們

>>>【說明】還是一如既往的,這篇文章是從我的個人博客裏挪過來的。原文參見: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”信號…. 

②  猜想的出來執行之後的效果麼?如果認爲會出現刷屏代碼的話,說明你已經懂了 

運行結果: 

[caption id="" align="aligncenter" width="397"]無盡地循環執行任務 無盡地循環執行任務 [/caption]

        很明顯上面的代碼不是我們所想要的,我們只要三個子進程即可!好吧,杜絕超生,那隻能計劃生育了。加個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)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章