很多Node.js初學(xué)者都會(huì)有這樣的疑惑,Node.js到底是單線(xiàn)程的還是多線(xiàn)程的?通過(guò)本章的學(xué)習(xí),能夠讓讀者較為清晰的理解Node.js對(duì)于單/多線(xiàn)程的關(guān)系和支持情況。同時(shí)本章還將列舉一些讓Node.js的web服務(wù)器線(xiàn)程阻塞的例子,最后會(huì)提供Node.js碰到這類(lèi)cpu密集型問(wèn)題的解決方案。
在學(xué)習(xí)本章之前,讀者需要對(duì)Node.js有一個(gè)初步的認(rèn)識(shí),熟悉Node.js基本語(yǔ)法、cluster模塊、child_process模塊和express框架;接觸過(guò)apache的http壓力測(cè)試工具ab;了解一般web服務(wù)器對(duì)于靜態(tài)文件的處理流程。
Node.js和PHP
早期有很多關(guān)于Node.js爭(zhēng)論的焦點(diǎn)都在它的單線(xiàn)程模型方面,在由Jani Hartikainen寫(xiě)的一篇著名的文章《PHP優(yōu)于Node.js的五大理由》中,更有一條矛頭直接指向Node.js單線(xiàn)程脆弱的問(wèn)題。
如果PHP代碼損壞,不會(huì)拖垮整個(gè)服務(wù)器。 PHP代碼只運(yùn)行在自己的進(jìn)程范圍中,當(dāng)某個(gè)請(qǐng)求顯示錯(cuò)誤時(shí),它只對(duì)特定的請(qǐng)求產(chǎn)生影響。而在Node.js環(huán)境中,所有的請(qǐng)求均在單一的進(jìn)程服務(wù)中,當(dāng)某個(gè)請(qǐng)求導(dǎo)致未知錯(cuò)誤時(shí),整個(gè)服務(wù)器都會(huì)受到影響。
Node.js和Apache+PHP還有一個(gè)非常不同的地方就是進(jìn)程的運(yùn)行時(shí)間長(zhǎng)短,當(dāng)然這一點(diǎn)也被此文作為一個(gè)PHP優(yōu)于Node.js的理由來(lái)寫(xiě)了。
PHP進(jìn)程短暫。在PHP中,每個(gè)進(jìn)程對(duì)請(qǐng)求持續(xù)的時(shí)間很短暫,這就意味著你不必為資源配置和內(nèi)存而擔(dān)憂(yōu)。而Node.js的進(jìn)程需要運(yùn)行很長(zhǎng)一段時(shí)間,你需要小心并妥善管理好內(nèi)存。比如,如果你忘記從全局?jǐn)?shù)據(jù)中刪除條目,這會(huì)輕易的導(dǎo)致內(nèi)存泄露。
在這里我們并不想引起一次關(guān)于PHP和Node.js孰優(yōu)孰劣的口水仗,PHP和Node.js各代表著一個(gè)互聯(lián)網(wǎng)時(shí)代的開(kāi)發(fā)語(yǔ)言,就如同我們討論跑車(chē)和越野車(chē)誰(shuí)更好一樣,它們都有自己所擅長(zhǎng)和適用的場(chǎng)景。我們可以通過(guò)下面這兩張圖深入理解一下PHP和Node.js對(duì)處理Http請(qǐng)求時(shí)的區(qū)別。
PHP的模型:
Node.js的模型:
所以你在編寫(xiě)Node.js代碼時(shí),要保持清醒的頭腦,任何一個(gè)隱藏著的異常被觸發(fā)后,都會(huì)將整個(gè)Node.js進(jìn)程擊潰。但是這樣的特性也為我們編寫(xiě)代碼帶來(lái)便利,比如同樣要實(shí)現(xiàn)一個(gè)簡(jiǎn)單的網(wǎng)站訪問(wèn)次數(shù)統(tǒng)計(jì),Node.js只需要在內(nèi)存里定義一個(gè)變量var count=0;,每次有用戶(hù)請(qǐng)求過(guò)來(lái)執(zhí)行count++;即可。
var http = require('http');
var count = 0;
http.createServer(function (request, response) {
? response.writeHead(200, {'Content-Type': 'text/plain'});
? response.end((++count).toString())
}).listen(8124);
console.log('Server running at http://127.0.0.1:8124/');
但是對(duì)于PHP來(lái)說(shuō)就需要使用第三方媒介來(lái)存儲(chǔ)這個(gè)count值了,比如創(chuàng)建一個(gè)count.txt文件來(lái)保存網(wǎng)站的訪問(wèn)次數(shù)。
? ? $counter_file = ("count.txt");
? ? $visits = file($counter_file);
? ? $visits[0]++;
? ? $fp = fopen($counter_file,"w");
? ? fputs($fp,"$visits[0]");
? ? fclose($fp);
? ? echo "$visits[0]";
?>
單線(xiàn)程的js
Google的V8 Javascript引擎已經(jīng)在Chrome瀏覽器里證明了它的性能,所以Node.js的作者Ryan Dahl選擇了v8作為Node.js的執(zhí)行引擎,v8賦予Node.js高效性能的同時(shí)也注定了Node.js和大名鼎鼎的Nginx一樣,都是以單線(xiàn)程為基礎(chǔ)的,當(dāng)然這也正是作者Ryan Dahl設(shè)計(jì)Node.js的初衷。
單線(xiàn)程的優(yōu)缺點(diǎn)
Node.js的單線(xiàn)程具有它的優(yōu)勢(shì),但也并非十全十美,在保持單線(xiàn)程模型的同時(shí),它是如何保證非阻塞的呢?
高性能
首先,單線(xiàn)程避免了傳統(tǒng)PHP那樣頻繁創(chuàng)建、切換線(xiàn)程的開(kāi)銷(xiāo),使執(zhí)行速度更加迅速。第二,資源占用小,如果有對(duì)Node.js的web服務(wù)器做過(guò)壓力測(cè)試的朋友可能發(fā)現(xiàn),Node.js在大負(fù)荷下對(duì)內(nèi)存占用仍然很低,同樣的負(fù)載PHP因?yàn)橐粋€(gè)請(qǐng)求一個(gè)線(xiàn)程的模型,將會(huì)占用大量的物理內(nèi)存,很可能會(huì)導(dǎo)致服務(wù)器因物理內(nèi)存耗盡而頻繁交換,失去響應(yīng)。
線(xiàn)程安全
單線(xiàn)程的js還保證了絕對(duì)的線(xiàn)程安全,不用擔(dān)心同一變量同時(shí)被多個(gè)線(xiàn)程進(jìn)行讀寫(xiě)而造成的程序崩潰。比如我們之前做的web訪問(wèn)統(tǒng)計(jì),因?yàn)閱尉€(xiàn)程的絕對(duì)線(xiàn)程安全,所以不可能存在同時(shí)對(duì)count變量進(jìn)行讀寫(xiě)的情況,我們的統(tǒng)計(jì)代碼就算是成百的并發(fā)用戶(hù)請(qǐng)求都不會(huì)出現(xiàn)問(wèn)題,相較PHP的那種存文件記錄訪問(wèn),就會(huì)面臨并發(fā)同時(shí)寫(xiě)文件的問(wèn)題。線(xiàn)程安全的同時(shí)也解放了開(kāi)發(fā)人員,免去了多線(xiàn)程編程中忘記對(duì)變量加鎖或者解鎖造成的悲劇。
單線(xiàn)程的異步和非阻塞
Node.js是單線(xiàn)程的,但是它如何做到I/O的異步和非阻塞的呢?其實(shí)Node.js在底層訪問(wèn)I/O還是多線(xiàn)程的,有興趣的朋友可以翻看Node.js的fs模塊的源碼,里面會(huì)用到libuv來(lái)處理I/O,所以在我們看來(lái)Node.js的代碼就是非阻塞和異步形式的。
阻塞/非阻塞與異步/同步是兩個(gè)不同的概念,同步不代表阻塞,但是阻塞肯定就是同步了。
舉個(gè)現(xiàn)實(shí)生活中的例子,我去食堂打飯,我選擇了A套餐,然后工作人員幫我去配餐,如果我就站在旁邊,等待工作人員給我配餐,這種情況就稱(chēng)之為同步;若工作人員幫我配餐的同時(shí),排在我后面的人就開(kāi)始點(diǎn)餐,這樣整個(gè)食堂的點(diǎn)餐服務(wù)并沒(méi)有因?yàn)槲以诘却鼳套餐而停止,這種情況就稱(chēng)之為非阻塞。這個(gè)例子就簡(jiǎn)單說(shuō)明了同步但非阻塞的情況。
再如果我在等待配餐的時(shí)候去買(mǎi)飲料,等聽(tīng)到叫號(hào)再回去拿套餐,此時(shí)我的飲料也已經(jīng)買(mǎi)好,這樣我在等待配餐的同時(shí)還執(zhí)行了買(mǎi)飲料的任務(wù),叫號(hào)就等于執(zhí)行了回調(diào),就是異步非阻塞了。
阻塞的單線(xiàn)程
既然Node.js是單線(xiàn)程異步非阻塞的,是不是我們就可以高枕無(wú)憂(yōu)了呢?
還是拿上面那個(gè)買(mǎi)套餐的例子,如果我在買(mǎi)飲料的時(shí)候,已經(jīng)叫我的號(hào)讓我去拿套餐,可是我等了好久才拿到飲料,所以我可能在大廳叫我的餐號(hào)之后很久才拿到A套餐,這也就是單線(xiàn)程的阻塞情況。
在瀏覽器中,js都是以單線(xiàn)程的方式運(yùn)行的,所以我們不用擔(dān)心js同時(shí)執(zhí)行帶來(lái)的沖突問(wèn)題,這對(duì)于我們編碼帶來(lái)很多的便利。
但是對(duì)于在服務(wù)端執(zhí)行的Node.js,它可能每秒有上百個(gè)請(qǐng)求需要處理,對(duì)于在瀏覽器端工作良好的單線(xiàn)程js是否也能同樣在服務(wù)端表現(xiàn)良好呢?
我們看如下代碼:
var start = Date.now();//獲取當(dāng)前時(shí)間戳
setTimeout(function () {
? ? console.log(Date.now() - start);
? ? for (var i = 0; i < 1000000000; i++){//執(zhí)行長(zhǎng)循環(huán)
? ? }
}, 1000);
setTimeout(function () {
? ? console.log(Date.now() - start);
}, 2000);
最終我們的打印結(jié)果是:(結(jié)果可能因?yàn)槟愕臋C(jī)器而不同)
1000
3738
對(duì)于我們期望2秒后執(zhí)行的setTimeout函數(shù)其實(shí)經(jīng)過(guò)了3738毫秒之后才執(zhí)行,換而言之,因?yàn)閳?zhí)行了一個(gè)很長(zhǎng)的for循環(huán),所以我們整個(gè)Node.js主線(xiàn)程被阻塞了,如果在我們處理100個(gè)用戶(hù)請(qǐng)求中,其中第一個(gè)有需要這樣大量的計(jì)算,那么其余99個(gè)就都會(huì)被延遲執(zhí)行。
其實(shí)雖然Node.js可以處理數(shù)以千記的并發(fā),但是一個(gè)Node.js進(jìn)程在某一時(shí)刻其實(shí)只是在處理一個(gè)請(qǐng)求。
單線(xiàn)程和多核
線(xiàn)程是cpu調(diào)度的一個(gè)基本單位,一個(gè)cpu同時(shí)只能執(zhí)行一個(gè)線(xiàn)程的任務(wù),同樣一個(gè)線(xiàn)程任務(wù)也只能在一個(gè)cpu上執(zhí)行,所以如果你運(yùn)行Node.js的機(jī)器是像i5,i7這樣多核cpu,那么將無(wú)法充分利用多核cpu的性能來(lái)為Node.js服務(wù)。
多線(xiàn)程
在C++、C#、python等其他語(yǔ)言都有與之對(duì)應(yīng)的多線(xiàn)程編程,有些時(shí)候這很有趣,帶給我們靈活的編程方式;但是也可能帶給我們一堆麻煩,需要學(xué)習(xí)更多的Api知識(shí),在編寫(xiě)更多代碼的同時(shí)也存在著更多的風(fēng)險(xiǎn),線(xiàn)程的切換和鎖也會(huì)造成系統(tǒng)資源的開(kāi)銷(xiāo)。
就像上面的那個(gè)例子,如果我們的Node.js有創(chuàng)建子線(xiàn)程的能力,那問(wèn)題就迎刃而解了:
var start = Date.now();
createThread(function () { //創(chuàng)建一個(gè)子線(xiàn)程執(zhí)行這10億次循環(huán)
? ? console.log(Date.now() - start);
? ? for (var i = 0; i < 1000000000; i++){}
});
setTimeout(function () { //因?yàn)?0億次循環(huán)是在子線(xiàn)程中執(zhí)行的,所以主線(xiàn)程不受影響
? ? console.log(Date.now() - start);
}, 2000);
可惜也可以說(shuō)可喜的是,Node.js的核心模塊并沒(méi)有提供這樣的api給我們,我們真的不想多線(xiàn)程又回歸回來(lái)。不過(guò)或許多線(xiàn)程真的能夠解決我們某方面的問(wèn)題。
tagg2模塊
Jorge Chamorro Bieling是tagg(Threads a gogo for Node.js)包的作者,他硬是利用phread庫(kù)和C語(yǔ)言讓Node.js支持了多線(xiàn)程的開(kāi)發(fā),我們看一下tagg模塊的簡(jiǎn)單示例:
var Threads = require('threads_a_gogo');//加載tagg包
function fibo(n) {//定義斐波那契數(shù)組計(jì)算函數(shù)
? ? return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1;
}
var t = Threads.create().eval(fibo);
t.eval('fibo(35)', function(err, result) {//將fibo(35)丟入子線(xiàn)程運(yùn)行
? ? if (err) throw err; //線(xiàn)程創(chuàng)建失敗
? ? console.log('fibo(35)=' + result);//打印fibo執(zhí)行35次的結(jié)果
});
console.log('not block');//打印信息了,表示沒(méi)有阻塞
上面這段代碼利用tagg包將fibo(35)這個(gè)計(jì)算丟入了子線(xiàn)程中進(jìn)行,保證了Node.js主線(xiàn)程的舒暢,當(dāng)子線(xiàn)程任務(wù)執(zhí)行完畢將會(huì)執(zhí)行主線(xiàn)程的回調(diào)函數(shù),把結(jié)果打印到屏幕上,執(zhí)行結(jié)果如下:
not block
fibo(35)=14930352
斐波那契數(shù)列,又稱(chēng)黃金分割數(shù)列,這個(gè)數(shù)列從第三項(xiàng)開(kāi)始,每一項(xiàng)都等于前兩項(xiàng)之和:0、1、1、2、3、5、8、13、21、……。
注意我們上面代碼的斐波那契數(shù)組算法并不是最優(yōu)算法,只是為了模擬cpu密集型計(jì)算任務(wù)。
由于tagg包目前只能在linux下安裝運(yùn)行,所以我fork了一個(gè)分支,修改了部分tagg包的代碼,發(fā)布了tagg2包。tagg2包同樣具有tagg包的多線(xiàn)程功能,采用新的node-gyp命令進(jìn)行編譯,同時(shí)它跨平臺(tái)支持,mac,linux,windows下都可以使用,對(duì)開(kāi)發(fā)人員的api也更加友好。安裝方法很簡(jiǎn)單,直接npm install tagg2。
一個(gè)利用tagg2計(jì)算斐波那契數(shù)組的http服務(wù)器代碼:
var express = require('express');
var tagg2 = require("tagg2");
var app = express();
var th_func = function(){//線(xiàn)程執(zhí)行函數(shù),以下內(nèi)容會(huì)在線(xiàn)程中執(zhí)行
? ? var fibo =function fibo (n) {//在子線(xiàn)程中定義fibo函數(shù)
? ? ? ? ? return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1;
? ? ? ? }
? ? var n = fibo(~~thread.buffer);//執(zhí)行fibo遞歸
? ? thread.end(n);//當(dāng)線(xiàn)程執(zhí)行完畢,執(zhí)行thread.end帶上計(jì)算結(jié)果回調(diào)主線(xiàn)程
};
app.get('/', function(req, res){
? ? var n = ~~req.query.n || 1;//獲取用戶(hù)請(qǐng)求參數(shù)
? ? var buf = new Buffer(n.toString());
? ? tagg2.create(th_func, {buffer:buf}, function(err,result){
? ? //創(chuàng)建一個(gè)js線(xiàn)程,傳入工作函數(shù),buffer參數(shù)以及回調(diào)函數(shù)
? ? ? ? if(err) return res.end(err);//如果線(xiàn)程創(chuàng)建失敗
? ? ? ? res.end(result.toString());//響應(yīng)線(xiàn)程執(zhí)行計(jì)算的結(jié)果
? ? })
});
app.listen(8124);
console.log('listen on 8124');
其中~~req.query.n表示將用戶(hù)傳遞的參數(shù)n取整,功能類(lèi)似Math.floor函數(shù)。
我們用express框架搭建了一個(gè)web服務(wù)器,根據(jù)用戶(hù)發(fā)送的參數(shù)n的值來(lái)創(chuàng)建子線(xiàn)程計(jì)算斐波那契數(shù)組,當(dāng)子線(xiàn)程計(jì)算完畢之后將結(jié)果響應(yīng)給客戶(hù)端。由于計(jì)算是丟入子線(xiàn)程中運(yùn)行的,所以整個(gè)主線(xiàn)程不會(huì)被阻塞,還是能夠繼續(xù)處理新請(qǐng)求的。
我們利用apache的http壓力測(cè)試工具ab來(lái)進(jìn)行一次簡(jiǎn)單的壓力測(cè)試,看看執(zhí)行斐波那契數(shù)組35次,100客戶(hù)端并發(fā)100個(gè)請(qǐng)求,我們的QPS (Query Per Second)每秒查詢(xún)率在多少。
ab的全稱(chēng)是ApacheBench,是Apache附帶的一個(gè)小工具,用于進(jìn)行HTTP服務(wù)器的性能測(cè)試,可以同時(shí)模擬多個(gè)并發(fā)請(qǐng)求。
我們的測(cè)試硬件:linux 2.6.4 4cpu 8G 64bit,網(wǎng)絡(luò)環(huán)境則是內(nèi)網(wǎng)。
ab壓力測(cè)試命令:
ab -c 100 -n 100 http://192.168.28.5:8124/?n=35
壓力測(cè)試結(jié)果:
Server Software:? ? ? ?
Server Hostname:? ? ? ? 192.168.28.5
Server Port:? ? ? ? ? ? 8124
Document Path:? ? ? ? ? /?n=35
Document Length:? ? ? ? 8 bytes
Concurrency Level:? ? ? 100
Time taken for tests:? 5.606 seconds
Complete requests:? ? ? 100
Failed requests:? ? ? ? 0
Write errors:? ? ? ? ? 0
Total transferred:? ? ? 10600 bytes
HTML transferred:? ? ? 800 bytes
Requests per second:? ? 17.84 [#/sec](mean)
Time per request:? ? ? 5605.769 [ms](mean)
Time per request:? ? ? 56.058 [ms](mean, across all concurrent requests)
Transfer rate:? ? ? ? ? 1.85 [Kbytes/sec] received
Connection Times (ms)
? ? ? ? ? ? ? min? mean[+/-sd] median? max
Connect:? ? ? ? 3? ? 4? 0.8? ? ? 4? ? ? 6
Processing:? 455 5367 599.7? 5526? ? 5598
Waiting:? ? ? 454 5367 599.7? 5526? ? 5598
Total:? ? ? ? 461 5372 599.3? 5531? ? 5602
Percentage of the requests served within a certain time (ms)
? 50%? 5531
? 66%? 5565
? 75%? 5577
? 80%? 5581
? 90%? 5592
? 95%? 5597
? 98%? 5600
? 99%? 5602
100%? 5602 (longest request)
我們看到Requests per second表示每秒我們服務(wù)器處理的任務(wù)數(shù)量,這里是17.84。第二個(gè)我們比較關(guān)心的是兩個(gè)Time per request結(jié)果,上面一行Time per request:5605.769 [ms](mean)表示當(dāng)前這個(gè)并發(fā)量下處理每組請(qǐng)求的時(shí)間,而下面這個(gè)Time per request:56.058 [ms](mean, across all concurrent requests)表示每個(gè)用戶(hù)平均處理時(shí)間,因?yàn)槲覀儽敬螠y(cè)試并發(fā)是100,所以結(jié)果正好是上一行的100分之1。得出本次測(cè)試平均每個(gè)用戶(hù)請(qǐng)求的平均等待時(shí)間為56.058 [ms]。
另外我們看下最后帶有百分比的列表,可以看到50%的用戶(hù)是在5531 ms以?xún)?nèi)返回的,最慢的也不過(guò)5602 ms,響應(yīng)延遲非常的平均。
我們?nèi)绻胏luster來(lái)啟動(dòng)4個(gè)進(jìn)程,是否可以充分利用cpu達(dá)到tagg2那樣的QPS呢?我們?cè)谕瑯拥木W(wǎng)絡(luò)環(huán)境和測(cè)試機(jī)上運(yùn)行如下代碼:
var cluster = require('cluster');//加載clustr模塊
var numCPUs = require('os').cpus().length;//設(shè)定啟動(dòng)進(jìn)程數(shù)為cpu個(gè)數(shù)
if (cluster.isMaster) {
? for (var i = 0; i < numCPUs; i++) {
? ? cluster.fork();//啟動(dòng)子進(jìn)程
? }
} else {
? ? var express = require('express');
? ? var app = express();
? ? var fibo = function fibo (n) {//定義斐波那契數(shù)組算法
? ? ? return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1;
? ? }
? ? app.get('/', function(req, res){
? ? ? var n = fibo(~~req.query.n || 1);//接收參數(shù)
? ? ? res.send(n.toString());
? ? });
? ? app.listen(8124);
? ? console.log('listen on 8124');
}
在終端屏幕上打印了4行信息:
listen on 8124
listen on 8124
listen on 8124
listen on 8124
我們成功啟動(dòng)了4個(gè)cluster之后,用同樣的ab壓力測(cè)試命令對(duì)8124端口進(jìn)行測(cè)試,結(jié)果如下:
Server Software:? ? ? ?
Server Hostname:? ? ? ? 192.168.28.5
Server Port:? ? ? ? ? ? 8124
Document Path:? ? ? ? ? /?n=35
Document Length:? ? ? ? 8 bytes
Concurrency Level:? ? ? 100
Time taken for tests:? 10.509 seconds
Complete requests:? ? ? 100
Failed requests:? ? ? ? 0
Write errors:? ? ? ? ? 0
Total transferred:? ? ? 16500 bytes
HTML transferred:? ? ? 800 bytes
Requests per second:? ? 9.52 [#/sec](mean)
Time per request:? ? ? 10508.755 [ms](mean)
Time per request:? ? ? 105.088 [ms](mean, across all concurrent requests)
Transfer rate:? ? ? ? ? 1.53 [Kbytes/sec] received
Connection Times (ms)
? ? ? ? ? ? ? min? mean[+/-sd] median? max
Connect:? ? ? ? 4? ? 5? 0.4? ? ? 5? ? ? 6
Processing:? 336 3539 2639.8? 2929? 10499
Waiting:? ? ? 335 3539 2639.9? 2929? 10499
Total:? ? ? ? 340 3544 2640.0? 2934? 10504
Percentage of the requests served within a certain time (ms)
? 50%? 2934
? 66%? 3763
? 75%? 4527
? 80%? 5153
? 90%? 8261
? 95%? 9719
? 98%? 10308
? 99%? 10504
100%? 10504 (longest request)
通過(guò)和上面tagg2包的測(cè)試結(jié)果對(duì)比,我們發(fā)現(xiàn)區(qū)別很大。首先每秒處理的任務(wù)數(shù)從17.84 [#/sec]下降到了9.52 [#/sec],這說(shuō)明我們web服務(wù)器整體的吞吐率下降了;然后每個(gè)用戶(hù)請(qǐng)求的平均等待時(shí)間也從56.058 [ms]提高到了105.088 [ms],用戶(hù)等待的時(shí)間也更長(zhǎng)了。
最后我們發(fā)現(xiàn)用戶(hù)請(qǐng)求處理的時(shí)長(zhǎng)非常的不均勻,50%的用戶(hù)在2934 ms內(nèi)返回了,最慢的等待達(dá)到了10504 ms。雖然我們使用了cluster啟動(dòng)了4個(gè)Node.js進(jìn)程處理用戶(hù)請(qǐng)求,但是對(duì)于每個(gè)Node.js進(jìn)程來(lái)說(shuō)還是單線(xiàn)程的,所以當(dāng)有4個(gè)用戶(hù)跑滿(mǎn)了4個(gè)Node.js的cluster進(jìn)程之后,新來(lái)的用戶(hù)請(qǐng)求就只能等待了,最后造成了先到的用戶(hù)處理時(shí)間短,后到的用戶(hù)請(qǐng)求處理時(shí)間比較長(zhǎng),就造成了用戶(hù)等待時(shí)間非常的不平均。
v8引擎
大家看到這里是不是開(kāi)始心潮澎湃,感覺(jué)js一統(tǒng)江湖的時(shí)代來(lái)臨了,單線(xiàn)程異步非阻塞的模型可以勝任大并發(fā),同時(shí)開(kāi)發(fā)也非常高效,多線(xiàn)程下的js可以承擔(dān)cpu密集型任務(wù),不會(huì)有主線(xiàn)程阻塞而引起的性能問(wèn)題。
但是,不論tagg還是tagg2包都是利用phtread庫(kù)和v8的v8::Isolate Class類(lèi)來(lái)實(shí)現(xiàn)js多線(xiàn)程功能的。
Isolate代表著一個(gè)獨(dú)立的v8引擎實(shí)例,v8的Isolate擁有完全分開(kāi)的狀態(tài),在一個(gè)Isolate實(shí)例中的對(duì)象不能夠在另外一個(gè)Isolate實(shí)例中使用。嵌入式開(kāi)發(fā)者可以在其他線(xiàn)程創(chuàng)建一些額外的Isolate實(shí)例并行運(yùn)行。在任何時(shí)刻,一個(gè)Isolate實(shí)例只能夠被一個(gè)線(xiàn)程進(jìn)行訪問(wèn),可以利用加鎖/解鎖進(jìn)行同步操作。
換而言之,我們?cè)谶M(jìn)行v8的嵌入式開(kāi)發(fā)時(shí),無(wú)法在多線(xiàn)程中訪問(wèn)js變量,這條規(guī)則將直接導(dǎo)致我們之前的tagg2里面線(xiàn)程執(zhí)行的函數(shù)無(wú)法使用Node.js的核心api,比如fs,crypto等模塊。如此看來(lái),tagg2包還是有它使用的局限性,針對(duì)一些可以使用js原生的大量計(jì)算或循環(huán)可以使用tagg2,Node.js核心api因?yàn)闊o(wú)法從主線(xiàn)程共享對(duì)象的關(guān)系,也就不能跨線(xiàn)程使用了。
libuv
最后,如果我們非要讓Node.js支持多線(xiàn)程,還是提倡使用官方的做法,利用libuv庫(kù)來(lái)實(shí)現(xiàn)。
libuv是一個(gè)跨平臺(tái)的異步I/O庫(kù),它主要用于Node.js的開(kāi)發(fā),同時(shí)他也被Mozilla's Rust language, Luvit, Julia, pyuv等使用。它主要包括了Event loops事件循環(huán),F(xiàn)ilesystem文件系統(tǒng),Networking網(wǎng)絡(luò)支持,Threads線(xiàn)程,Processes進(jìn)程,Utilities其他工具。
在Node.js核心api中的異步多線(xiàn)程大多是使用libuv來(lái)實(shí)現(xiàn)的,下一章將帶領(lǐng)大家開(kāi)發(fā)一個(gè)讓Node.js支持多線(xiàn)程并基于libuv的Node.js包。
多進(jìn)程
在支持html5的瀏覽器里,我們可以使用webworker來(lái)將一些耗時(shí)的計(jì)算丟入worker進(jìn)程中執(zhí)行,這樣主進(jìn)程就不會(huì)阻塞,用戶(hù)也就不會(huì)有卡頓的感覺(jué)了。在Node.js中是否也可以使用這類(lèi)技術(shù),保證主線(xiàn)程的通暢呢?
cluster
cluster可以用來(lái)讓Node.js充分利用多核cpu的性能,同時(shí)也可以讓Node.js程序更加健壯,官網(wǎng)上的cluster示例已經(jīng)告訴我們?nèi)绾沃匦聠?dòng)一個(gè)因?yàn)楫惓6紳⒌淖舆M(jìn)程。
webworker
想要像在瀏覽器端那樣啟動(dòng)worker進(jìn)程,我們需要利用Node.js核心api里的child_process模塊。child_process模塊提供了fork的方法,可以啟動(dòng)一個(gè)Node.js文件,將它作為worker進(jìn)程,當(dāng)worker進(jìn)程工作完畢,把結(jié)果通過(guò)send方法傳遞給主進(jìn)程,然后自動(dòng)退出,這樣我們就利用了多進(jìn)程來(lái)解決主線(xiàn)程阻塞的問(wèn)題。
我們先啟動(dòng)一個(gè)web服務(wù),還是接收參數(shù)計(jì)算斐波那契數(shù)組:
var express = require('express');
var fork = require('child_process').fork;
var app = express();
app.get('/', function(req, res){
? var worker = fork('./work_fibo.js') //創(chuàng)建一個(gè)工作進(jìn)程
? worker.on('message', function(m) {//接收工作進(jìn)程計(jì)算結(jié)果
? ? ? ? ? if('object' === typeof m && m.type === 'fibo'){
? ? ? ? ? ? ? ? ? worker.kill();//發(fā)送殺死進(jìn)程的信號(hào)
? ? ? ? ? ? ? ? ? res.send(m.result.toString());//將結(jié)果返回客戶(hù)端
? ? ? ? ? }
? });
? worker.send({type:'fibo',num:~~req.query.n || 1});
? //發(fā)送給工作進(jìn)程計(jì)算fibo的數(shù)量
});
app.listen(8124);
我們通過(guò)express監(jiān)聽(tīng)8124端口,對(duì)每個(gè)用戶(hù)的請(qǐng)求都會(huì)去fork一個(gè)子進(jìn)程,通過(guò)調(diào)用worker.send方法將參數(shù)n傳遞給子進(jìn)程,同時(shí)監(jiān)聽(tīng)子進(jìn)程發(fā)送消息的message事件,將結(jié)果響應(yīng)給客戶(hù)端。
下面是被fork的work_fibo.js文件內(nèi)容:
var fibo = function fibo (n) {//定義算法
? return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1;
}
process.on('message', function(m) {
//接收主進(jìn)程發(fā)送過(guò)來(lái)的消息
? ? ? ? ? if(typeof m === 'object' && m.type === 'fibo'){
? ? ? ? ? ? ? ? ? var num = fibo(~~m.num);
? ? ? ? ? ? ? ? ? //計(jì)算jibo
? ? ? ? ? ? ? ? ? process.send({type: 'fibo',result:num})
? ? ? ? ? ? ? ? ? //計(jì)算完畢返回結(jié)果? ? ? ?
? ? ? ? ? }
});
process.on('SIGHUP', function() {
? ? ? ? process.exit();//收到kill信息,進(jìn)程退出
});
我們先定義函數(shù)fibo用來(lái)計(jì)算斐波那契數(shù)組,然后監(jiān)聽(tīng)了主線(xiàn)程發(fā)來(lái)的消息,計(jì)算完畢之后將結(jié)果send到主線(xiàn)程。同時(shí)還監(jiān)聽(tīng)process的SIGHUP事件,觸發(fā)此事件就進(jìn)程退出。
這里我們有一點(diǎn)需要注意,主線(xiàn)程的kill方法并不是真的使子進(jìn)程退出,而是會(huì)觸發(fā)子進(jìn)程的SIGHUP事件,真正的退出還是依靠process.exit();。
下面我們用ab 命令測(cè)試一下多進(jìn)程方案的處理性能和用戶(hù)請(qǐng)求延遲,測(cè)試環(huán)境不變,還是100個(gè)并發(fā)100次請(qǐng)求,計(jì)算斐波那切數(shù)組第35位:
Server Software:? ? ? ?
Server Hostname:? ? ? ? 192.168.28.5
Server Port:? ? ? ? ? ? 8124
Document Path:? ? ? ? ? /?n=35
Document Length:? ? ? ? 8 bytes
Concurrency Level:? ? ? 100
Time taken for tests:? 7.036 seconds
Complete requests:? ? ? 100
Failed requests:? ? ? ? 0
Write errors:? ? ? ? ? 0
Total transferred:? ? ? 16500 bytes
HTML transferred:? ? ? 800 bytes
Requests per second:? ? 14.21 [#/sec](mean)
Time per request:? ? ? 7035.775 [ms](mean)
Time per request:? ? ? 70.358 [ms](mean, across all concurrent requests)
Transfer rate:? ? ? ? ? 2.29 [Kbytes/sec] received
Connection Times (ms)
? ? ? ? ? ? ? min? mean[+/-sd] median? max
Connect:? ? ? ? 4? ? 4? 0.2? ? ? 4? ? ? 5
Processing:? 4269 5855 970.3? 6132? ? 7027
Waiting:? ? 4269 5855 970.3? 6132? ? 7027
Total:? ? ? 4273 5860 970.3? 6136? ? 7032
Percentage of the requests served within a certain time (ms)
? 50%? 6136
? 66%? 6561
? 75%? 6781
? 80%? 6857
? 90%? 6968
? 95%? 7003
? 98%? 7017
? 99%? 7032
100%? 7032 (longest request)
壓力測(cè)試結(jié)果QPS約為14.21,相比cluster來(lái)說(shuō),還是快了很多,每個(gè)用戶(hù)請(qǐng)求的延遲都很平均,因?yàn)檫M(jìn)程的創(chuàng)建和銷(xiāo)毀的開(kāi)銷(xiāo)要大于線(xiàn)程,所以在性能方面略低于tagg2,不過(guò)相對(duì)于cluster方案,這樣的提升還是令我們滿(mǎn)意的。
換一種思路
使用child_process模塊的fork方法確實(shí)可以讓我們很好的解決單線(xiàn)程對(duì)cpu密集型任務(wù)的阻塞問(wèn)題,同時(shí)又沒(méi)有tagg2包那樣無(wú)法使用Node.js核心api的限制。
但是如果我的worker具有多樣性,每次在利用child_process模塊解決問(wèn)題時(shí)都需要去創(chuàng)建一個(gè)worker.js的工作函數(shù)文件,有點(diǎn)麻煩。我們是不是可以更加簡(jiǎn)單一些呢?
在我們啟動(dòng)Node.js程序時(shí),node命令可以帶上-e這個(gè)參數(shù),它將直接執(zhí)行-e后面的字符串,如下代碼就將打印出hello world。
node -e "console.log('hello world')"
合理的利用這個(gè)特性,我們就可以免去每次都創(chuàng)建一個(gè)文件的麻煩。
var express = require('express');
var spawn = require('child_process').spawn;
var app = express();
var spawn_worker = function(n,end){//定義工作函數(shù)
? ? var fibo = function fibo (n) {
? ? ? return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1;
? ? }
? ? end(fibo(n));
? }
var spawn_end = function(result){//定義工作函數(shù)結(jié)束的回調(diào)函數(shù)參數(shù)
? ? console.log(result);
? ? process.exit();
}
app.get('/', function(req, res){
? var n = ~~req.query.n || 1;
? //拼接-e后面的參數(shù)
? var spawn_cmd = '('+spawn_worker.toString()+'('+n+','+spawn_end.toString()+'));'
? console.log(spawn_cmd);//注意這個(gè)打印結(jié)果
? var worker = spawn('node',['-e',spawn_cmd]);//執(zhí)行node -e "xxx"命令
? var fibo_res = '';
? worker.stdout.on('data', function (data) { //接收工作函數(shù)的返回
? ? ? fibo_res += data.toString();
? });
? worker.on('close', function (code) {//將結(jié)果響應(yīng)給客戶(hù)端
? ? ? res.send(fibo_res);
? });
});
app.listen(8124);
代碼很簡(jiǎn)單,我們主要關(guān)注3個(gè)地方。
第一、我們定義了spawn_worker函數(shù),他其實(shí)就是將會(huì)在-e后面執(zhí)行的工作函數(shù),所以我們把計(jì)算斐波那契數(shù)組的算法定義在內(nèi),spawn_worker函數(shù)接收2個(gè)參數(shù),第一個(gè)參數(shù)n表示客戶(hù)請(qǐng)求要計(jì)算的斐波那契數(shù)組的位數(shù),第二個(gè)end參數(shù)是一個(gè)函數(shù),如果計(jì)算完畢則執(zhí)行end,將結(jié)果傳回主線(xiàn)程;
第二、真正當(dāng)Node.js腳步執(zhí)行的字符串其實(shí)就是spawn_cmd里的內(nèi)容,它的內(nèi)容我們通過(guò)運(yùn)行之后的打印信息,很容易就能明白;
第三、我們利用child_process的spawn方法,類(lèi)似在命令行里執(zhí)行了node -e "js code",啟動(dòng)Node.js工作進(jìn)程,同時(shí)監(jiān)聽(tīng)子進(jìn)程的標(biāo)準(zhǔn)輸出,將數(shù)據(jù)保存起來(lái),當(dāng)子進(jìn)程退出之后把結(jié)果響應(yīng)給用戶(hù)。
現(xiàn)在主要的焦點(diǎn)就是變量spawn_cmd到底保存了什么,我們打開(kāi)瀏覽器在地址欄里輸入:
http://127.0.0.1:8124/?n=35
下面就是程序運(yùn)行之后的打印信息,
(function (n,end){
? ? var fibo = function fibo (n) {
? ? ? return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1;
? ? }
? ? end(fibo(n));
? }(35,function (result){
? ? ? console.log(result);
? ? ? process.exit();
}));
對(duì)于在子進(jìn)程執(zhí)行的工作函數(shù)的兩個(gè)參數(shù)n和end現(xiàn)在一目了然,n代表著用戶(hù)請(qǐng)求的參數(shù),期望獲得的斐波那契數(shù)組的位數(shù),而end參數(shù)則是一個(gè)匿名函數(shù),在標(biāo)準(zhǔn)輸出中打印計(jì)算結(jié)果然后退出進(jìn)程。
node -e命令雖然可以減少創(chuàng)建文件的麻煩,但同時(shí)它也有命令行長(zhǎng)度的限制,這個(gè)值各個(gè)系統(tǒng)都不相同,我們通過(guò)命令getconf ARG_MAX來(lái)獲得最大命令長(zhǎng)度,例如:MAC OSX下是262,144 byte,而我的linux虛擬機(jī)則是131072 byte。
多進(jìn)程和多線(xiàn)程
大部分多線(xiàn)程解決cpu密集型任務(wù)的方案都可以用我們之前討論的多進(jìn)程方案來(lái)替代,但是有一些比較特殊的場(chǎng)景多線(xiàn)程的優(yōu)勢(shì)就發(fā)揮出來(lái)了,下面就拿我們最常見(jiàn)的http web服務(wù)器響應(yīng)一個(gè)小的靜態(tài)文件作為例子。
以express處理小型靜態(tài)文件為例,大致的處理流程如下: 1、首先獲取文件狀態(tài),判斷文件的修改時(shí)間或者判斷etag來(lái)確定是否響應(yīng)304給客戶(hù)端,讓客戶(hù)端繼續(xù)使用本地緩存。 2、如果緩存已經(jīng)失效或者客戶(hù)端沒(méi)有緩存,就需要獲取文件的內(nèi)容到buffer中,為響應(yīng)作準(zhǔn)備。 3、然后判斷文件的MIME類(lèi)型,如果是類(lèi)似html,js,css等靜態(tài)資源,還需要gzip壓縮之后傳輸給客戶(hù)端 4、最后將gzip壓縮完成的靜態(tài)文件響應(yīng)給客戶(hù)端。
下面是一個(gè)正常成功的Node.js處理靜態(tài)資源無(wú)緩存流程圖:
這個(gè)流程中的(2),(3),(4)步都經(jīng)歷了從js到C++ ,打開(kāi)和釋放文件,還有調(diào)用了zlib庫(kù)的gzip算法,其中每個(gè)異步的算法都會(huì)有創(chuàng)建和銷(xiāo)毀線(xiàn)程的開(kāi)銷(xiāo),所以這樣也是大家詬病Node.js處理靜態(tài)文件不給力的原因之一。
為了改善這個(gè)問(wèn)題,我之前有利用libuv庫(kù)開(kāi)發(fā)了一個(gè)改善Node.js的http/https處理靜態(tài)文件的包,名為ifile,ifile包,之所以可以加速Node.js的靜態(tài)文件處理性能,主要是減少了js和C++的互相調(diào)用,以及頻繁的創(chuàng)建和銷(xiāo)毀線(xiàn)程的開(kāi)銷(xiāo),下圖是ifile包處理一個(gè)靜態(tài)無(wú)緩存資源的流程圖:
由于全部工作都是在libuv的子線(xiàn)程中執(zhí)行的,所以Node.js主線(xiàn)程不會(huì)阻塞,當(dāng)然性能也會(huì)大幅提升了,使用ifile包非常簡(jiǎn)單,它能夠和express無(wú)縫的對(duì)接。
var express = require('express');
var ifile = require("ifile");
var app = express();? ?
app.use(ifile.connect());? //默認(rèn)值是 [['/static',__dirname]];? ? ? ?
app.listen(8124);
上面這4行代碼就可以讓express把靜態(tài)資源交給ifile包來(lái)處理了,我們?cè)谶@里對(duì)它進(jìn)行了一個(gè)簡(jiǎn)單的壓力測(cè)試,測(cè)試用例為響應(yīng)一個(gè)大小為92kb的jquery.1.7.1.min.js文件,測(cè)試命令:
ab -c 500 -n 5000 -H "Accept-Encoding: gzip"
http://192.168.28.5:8124/static/jquery.1.7.1.min.js
由于在ab命令中我們加入了-H "Accept-Encoding: gzip",表示響應(yīng)的靜態(tài)文件希望是gzip壓縮之后的,所以ifile將會(huì)把壓縮之后的jquery.1.7.1.min.js文件響應(yīng)給客戶(hù)端。結(jié)果如下:
Server Software:? ? ? ?
Server Hostname:? ? ? ? 192.168.28.5
Server Port:? ? ? ? ? ? 8124
Document Path:? ? ? ? ? /static/jquery.1.7.1.min.js
Document Length:? ? ? ? 33016 bytes
Concurrency Level:? ? ? 500
Time taken for tests:? 9.222 seconds
Complete requests:? ? ? 5000
Failed requests:? ? ? ? 0
Write errors:? ? ? ? ? 0
Total transferred:? ? ? 166495000 bytes
HTML transferred:? ? ? 165080000 bytes
Requests per second:? ? 542.16 [#/sec](mean)
Time per request:? ? ? 922.232 [ms](mean)
Time per request:? ? ? 1.844 [ms](mean, across all concurrent requests)
Transfer rate:? ? ? ? ? 17630.35 [Kbytes/sec] received
Connection Times (ms)
? ? ? ? ? ? ? min? mean[+/-sd] median? max
Connect:? ? ? ? 0? 49 210.2? ? ? 1? ? 1003
Processing:? 191? 829 128.6? ? 870? ? 1367
Waiting:? ? ? 150? 824 128.5? ? 869? ? 1091
Total:? ? ? ? 221? 878 230.7? ? 873? ? 1921
Percentage of the requests served within a certain time (ms)
? 50%? ? 873
? 66%? ? 878
? 75%? ? 881
? 80%? ? 885
? 90%? ? 918
? 95%? 1109
? 98%? 1815
? 99%? 1875
100%? 1921 (longest request)
我們首先看到Document Length一項(xiàng)結(jié)果為33016 bytes說(shuō)明我們的jquery文件已經(jīng)被成功的gzip壓縮,因?yàn)樵次募笮∈?2kb;其次,我們最關(guān)心的Requests per second:542.16 [#/sec](mean),說(shuō)明我們每秒能處理542個(gè)任務(wù);最后,我們看到,在這樣的壓力情況下,平均每個(gè)用戶(hù)的延遲在1.844 [ms]。
我們看下使用express框架處理這樣的壓力會(huì)是什么樣的結(jié)果,express測(cè)試代碼如下:
var express = require('express');
var app = express();
app.use(express.compress());//支持gzip
app.use('/static', express.static(__dirname + '/static'));
app.listen(8124);
代碼同樣非常簡(jiǎn)單,注意這里我們使用:
app.use('/static', express.static(__dirname + '/static'));
而不是:
app.use(express.static(__dirname));
后者每個(gè)請(qǐng)求都會(huì)去匹配一次文件是否存在,而前者只有請(qǐng)求url是/static開(kāi)頭的才會(huì)去匹配靜態(tài)資源,所以前者效率更高一些。然后我們執(zhí)行相同的ab壓力測(cè)試命令看下結(jié)果:
Server Software:? ? ? ?
Server Hostname:? ? ? ? 192.168.28.5
Server Port:? ? ? ? ? ? 8124
Document Path:? ? ? ? ? /static/jquery.1.7.1.min.js
Document Length:? ? ? ? 33064 bytes
Concurrency Level:? ? ? 500
Time taken for tests:? 16.665 seconds
Complete requests:? ? ? 5000
Failed requests:? ? ? ? 0
Write errors:? ? ? ? ? 0
Total transferred:? ? ? 166890000 bytes
HTML transferred:? ? ? 165320000 bytes
Requests per second:? ? 300.03 [#/sec](mean)
Time per request:? ? ? 1666.517 [ms](mean)
Time per request:? ? ? 3.333 [ms](mean, across all concurrent requests)
Transfer rate:? ? ? ? ? 9779.59 [Kbytes/sec] received
Connection Times (ms)
? ? ? ? ? ? ? min? mean[+/-sd] median? max
Connect:? ? ? ? 0? 173 539.8? ? ? 1? ? 7003
Processing:? 509? 886 350.5? ? 809? ? 9366
Waiting:? ? ? 238? 476 277.9? ? 426? ? 9361
Total:? ? ? ? 510 1059 632.9? ? 825? ? 9367
Percentage of the requests served within a certain time (ms)
? 50%? ? 825
? 66%? ? 908
? 75%? 1201
? 80%? 1446
? 90%? 1820
? 95%? 1952
? 98%? 2560
? 99%? 3737
100%? 9367 (longest request)
同樣分析一下結(jié)果,Document Length:33064 bytes表示文檔大小為33064 bytes,說(shuō)明我們的gzip起作用了,每秒處理任務(wù)數(shù)從ifile包的542下降到了300,最長(zhǎng)用戶(hù)等待時(shí)間也延長(zhǎng)到了9367 ms,可見(jiàn)我們的努力起到了立竿見(jiàn)影的作用,js和C++互相調(diào)用以及線(xiàn)程的創(chuàng)建和釋放并不是沒(méi)有損耗的。
但是當(dāng)我在express的谷歌論壇里貼上這些測(cè)試結(jié)果,并宣傳ifile包的時(shí)候,express的作者TJ,給出了不一樣的評(píng)價(jià),他在回復(fù)中說(shuō)道:
請(qǐng)牢記你可能不需要這么高等級(jí)吞吐率的系統(tǒng),就算是每月百萬(wàn)級(jí)別下載量的npm網(wǎng)站,也僅僅每秒處理17個(gè)請(qǐng)求而已,這樣的壓力甚至于PHP也可以處理掉(又黑了一把php)。
確實(shí)如TJ所說(shuō),性能只是我們項(xiàng)目的指標(biāo)之一而非全部,一味的去追求高性能并不是很理智。
ifile包開(kāi)源項(xiàng)目地址:https://github.com/DoubleSpout/ifile
總結(jié)
單線(xiàn)程的Node.js給我們編碼帶來(lái)了太多的便利和樂(lè)趣,我們應(yīng)該時(shí)刻保持清醒的頭腦,在寫(xiě)Node.js代碼中切不可與PHP混淆,任何一個(gè)隱藏的問(wèn)題都可能擊潰整個(gè)線(xiàn)上正在運(yùn)行的Node.js程序。
單線(xiàn)程異步的Node.js不代表不會(huì)阻塞,在主線(xiàn)程做過(guò)多的任務(wù)可能會(huì)導(dǎo)致主線(xiàn)程的卡死,影響整個(gè)程序的性能,所以我們要非常小心的處理大量的循環(huán),字符串拼接和浮點(diǎn)運(yùn)算等cpu密集型任務(wù),合理的利用各種技術(shù)把任務(wù)丟給子線(xiàn)程或子進(jìn)程去完成,保持Node.js主線(xiàn)程的暢通。
線(xiàn)程/進(jìn)程的使用并不是沒(méi)有開(kāi)銷(xiāo)的,盡可能減少創(chuàng)建和銷(xiāo)毀線(xiàn)程/進(jìn)程的次數(shù),可以提升我們系統(tǒng)整體的性能和出錯(cuò)的概率。
最后請(qǐng)不要一味的追求高性能和高并發(fā),因?yàn)槲覀兛赡懿恍枰到y(tǒng)具有那么大的吞吐率。高效,敏捷,低成本的開(kāi)發(fā)才是項(xiàng)目所需要的,這也是為什么Node.js能夠在眾多開(kāi)發(fā)語(yǔ)言中脫穎而出的關(guān)鍵。
參考文獻(xiàn):
http://smashingnode.comSmashing Node.JS By Guillermo Rauch
http://bjouhier.wordpress.com/2012/03/11/fibers-and-threads-in-node-js-what-forFibers and Threads in node.js – what for? By Bruno's Ramblings
https://github.com/xk/node-threads-a-gogoTAGG: Threads à gogo for Node.js By Jorge Chamorro Bieling
https://code.google.com/p/v8/Google v8
https://github.com/joyent/libuvlibuv by joyent
本文轉(zhuǎn)自:https://www.cnblogs.com/chris-oil/p/5339305.html