node從他推出至今,充滿讚美和飽受詬病的都是其單線程模型,全部的任務都在一個線程中完成(I/O等例外),優點的地方天然是免去了頻繁切換線程的開銷,以及減小資源互搶的問題等等,可是當nodejs面對cpu密集型模型的時候就力不從心了。儘管node擁有異步機制,能夠把一些耗時算法丟入eventloop等待下個事件循環再作,可是由於其任然是單線程模型,因此終究會形成阻塞。php
先解釋一下兩個名詞,Fibers 和 Threads。
Fibers 又稱纖程,能夠理解爲協同程序,相似py和lua都有這樣的模型。使用Fibers能夠避免對資源的互搶,減小cpu和內存的消耗,可是Fibers並不可以真正的並行執行,同一時刻只有一個Fibers在執行,若是在其中一個Fibers中執行過多的cpu操做或者寫了個死循環,則整個主程序將卡死住。node中的異步事件循環模型就有點象這個。node
Threads 又稱線程,他能夠在同一時刻並行的執行,他們共享主進程的內存,在其中某一時刻某一個threads鎖死了,是不會影響主線程以及其餘線程的執行。可是爲了實現這個模型,咱們不得不消耗更多的內存和cpu爲線程切換的開銷,同時也存在可能多個線程對同一內存單元進行讀寫而形成程序崩潰的問題。linux
不少讓node支持多線程的方法是使用c/c++的addon來實現,在須要進行cpu密集型計算的地方,把js代碼改寫成c/c++代碼,可是若是開發人員對c++不是很熟悉,一來開發效率會下降很多,二來也容易出bug,並且咱們知道在addon中的c++代碼除了編譯出錯外,是很難調試的,畢竟沒有vs調試c++代碼方便。c++
使人振奮的消息,咱們爲何不讓node也支持多線程模型呢?因而Jorge爲咱們開發出了一個讓node支持多線程模型的模塊:threads_a_gogo
github地址:https://github.com/xk/node-threads-a-gogogit
有了threads-a-gogo(如下簡稱TAGG)這個模塊以後,咱們可讓node作更多的事情,我記得之前我看過一篇文章,說node只能應付i/o密集型場景,在cpu密集型場景將完敗給apache,由於apache是爲每個請求起一條線程的,因此在處理cpu密集型任務時一個線程的高強度計算不會很大程度的影響其餘線程,相似的還有php的fastcgi,這也是不少拿node和php進行比較時,php的擁護者們一直提出的理論。github
咱們先來作一個簡單的測試,用咱們suqian大大最喜歡的斐波那契數組來看一下,加入了多線程的node有多麼的強悍:(測試機器爲4CPU)
沒有使用TAGG的正常狀況,異步也幫不了咱們應對cpu密集型任務算法
function fibo (n) { return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1; } var n=8 function back(){ if(!--n) return console.timeEnd('no thread'); } console.time('no thread'); process.nextTick(function(){ console.log(fibo (40)); back(); }) process.nextTick(function(){ console.log(fibo (40)); back(); }) process.nextTick(function(){ console.log(fibo (40)); back(); }) process.nextTick(function(){ console.log(fibo (40)); back(); }) process.nextTick(function(){ console.log(fibo (40)); back(); })process.nextTick(function(){ console.log(fibo (40)); back();})process.nextTick(function(){ console.log(fibo (40)); back();})process.nextTick(function(){ console.log(fibo (40)); back();})
咱們模擬了8個異步的行爲,測試用的node v0.8.16版本,因此 process.nextTick仍是異步方法。最後咱們輸出結果爲:apache
165580141165580141165580141165580141165580141165580141165580141165580141no thread: 23346ms
接下來咱們使用TAGG模塊來測試一樣的執行8次斐波那契數組計算,看當作績如何?windows
function fibo (n) { return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1;}console.time('8 thread');var numThreads= 8; //建立線程池,最大數爲8var threadPool= require('threads_a_gogo').createPool(numThreads).all.eval(fibo); //爲線程池註冊程序var i=8;var cb = function(err,data){ //註冊線程執行完畢的回調函數 console.log(data); if(!--i){ threadPool.destroy(); console.timeEnd('8 thread'); }}threadPool.any.eval('fibo(40)', cb); //開始向線程池中執行fibo(40)這個任務threadPool.any.eval('fibo(40)', cb);threadPool.any.eval('fibo(40)', cb);threadPool.any.eval('fibo(40)', cb);threadPool.any.eval('fibo(40)', cb);threadPool.any.eval('fibo(40)', cb);threadPool.any.eval('fibo(40)', cb);threadPool.any.eval('fibo(40)', cb);
最重的結果:api
1655801411655801411655801411655801411655801411655801411655801411655801418 thread: 9510ms
相比不使用多線程模型的node,使用了TAGG模塊以後,咱們在4CPU服務器上的測試結果要快上一倍還不止。
到這裏咱們看上去找到了一個比較完美的解決方案應對CPU密集型任務,可是可能有同窗會說,我可使用cluster來作相同的事情,下面咱們來作一個使用cluster計算這些任務的狀況:
var cluster = require('cluster');var numCPUs = 8;function fibo (n) { return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1;}console.time('8 cluster');if (cluster.isMaster) { // Fork workers. for (var i = 0; i < numCPUs; i++) { cluster.fork(); } var i = 8; cluster.on('exit', function(worker, code, signal) { if(!--i){ console.timeEnd('8 cluster'); process.exit(0); } });} else { console.log(fibo (40)); process.exit(0);}
代碼上的複雜程度比使用TAGG要高的多,並且若是是動態計算斐波那契數組的結果,編碼將更加困難,須要在fork時掛上不一樣的參數,出錯的概率也更大。同時還有更重要的一個事情,若是是建立一個http服務器,若是4個cluster都在計算fibo,那第5個請求node將沒法處理,而是用TAGG則仍是可以正常處理的,因此cluster並不能解決單線程模型的cpu密集計算帶來的阻塞問題,咱們看下測試結果:
1655801411655801411655801411655801411655801411655801411655801411655801418 cluster: 11925ms
TAGG模塊還有其餘更多的功能,好比事件觸發,平滑退出,查看線程工做狀態等等,總之TAGG模塊給node注入了新的活力,讓node一直飽受詬病的處理cpu密集任務問題獲得了一個妥善的解決,就算你不擅長c++代碼,也可以輕鬆編寫出多線程的真正的非阻塞node程序了。
原文連接:http://cnodejs.org/topic/518b679763e9f8a5424406e9
最後分享一篇乾貨文章,至關很精彩的一篇博客:
Fibers and Threads in node.js – what for?
tagg2,nodejs多線程模塊,更好的api,支持nodejs原生模塊,跨平臺支持,windows,linux和mac
跨平臺模塊tagg2,讓node多線程支持