Node.js是單線程的,基於事件循環,非阻塞 IO的。事件循環中使用一個事件隊列,在每個時間點上,系統只會處理一個事件,即使電腦有多個CPU核心,也無法同時並行的處理多個事件。因此,node.js適合處理I/O型的應用,不適合那種CPU運算密集型的應用。在I/O型的應用中,給每一個輸入輸出定義一個回調函數,node.js會自動將其加入到事件輪詢的處理隊列里,當I/O操作完成后,這個回調函數會被觸發,系統會繼續處理其他的請求。
在這里用debuggable.com上的那個文章中的一段比喻來講,非常容易理解。如下:
我們寫的js代碼就像是一個國王,而nodejs給國王提供了很多仆人。早上,一個仆人叫醒了國王,問他有什么需要。國王給他一份清單,上面列舉了所有需要完成的任務,然后睡回籠覺去了。當國王回去睡覺之后,仆人才離開國王,拿着清單,給其它的仆人一個個布置任務。仆人們各自忙各自的去了,直到完成了自己的任務后,才回來把結果稟告給國王。國王一次只召見一個人,其它的人就在外面排着隊等着。國王處理完這個結果后,可能給他布置一個新的任務,或者就直接讓他走了,然后再召見下一個人。等所有的結果都處理完了,國王就繼續睡覺去了。直接有新的仆人完成任務后過來找他。這就是國王的幸福生活。
process.nextTick(callback)
功能:在事件循環的下一次循環中調用 callback 回調函數。效果是將一個函數推遲到代碼書寫的下一個同步方法執行完畢時或異步方法的事件回調函數開始執行時;與setTimeout(fn, 0) 函數的功能類似,但它的效率高多了。
基於node.js的事件循環分析,每一次循環就是一次tick,每一次tick時,v8引擎從事件隊列中取出所有事件依次進行處理,如果遇到nextTick事件,則將其加入到事件隊尾,等待下一次tick到來時執行;造成的結果是,nextTick事件被延遲執行;以下是nextTick源碼
從這幾行代碼中,我們可以看出很多信息:
-
nextTick的確是把某任務放在隊列的最后(array.push)
-
nodejs在執行任務時,會一次性把隊列中所有任務都拿出來,依次執行
-
如果全部順利完成,則刪除剛才取出的所有任務,等待下一次執行
-
如果中途出錯,則刪除已經完成的任務和出錯的任務,等待下次執行
-
如果第一個就出錯,則throw error
下面看一下應用場景(包含計算密集型操作,將其進行遞歸處理,而不阻塞進程):
-
var http = require('http');
-
var wait = function (mils) {
-
var now = new Date;
-
while (new Date - now <= mils);
-
};
-
function compute() {
-
// performs complicated calculations continuously
-
console.log('start computing');
-
wait(1000);
-
console.log('working for 1s, nexttick');
-
process.nextTick(compute);
-
}
-
http.createServer(function (req, res) {
-
console.log('new request');
-
res.writeHead(200, {'Content-Type': 'text/plain'});
-
res.end('Hello World');
-
}).listen(5000, '127.0.0.1');
-
compute();
1、其中compute是一個密集計算的函數,我們把它變為可遞歸的,每一步需要1秒(使用wait來代替密集運行)。執行完一次后,通過process.nextTick把下一次的執行放在隊列的尾部,轉而去處理已經處於等待中的客戶端請求。這樣就可以同時兼顧兩種任務,讓它們都有機會執行。
關於node.js中處理計算密集型,可以參考以下幾種方法:
-
c/c++的addon來實現,在需要進行cpu密集型計算的地方,把js代碼改寫成c/c++代碼;對於不熟悉C++代碼,成本較高
-
使用cluster創建多進程處理,但編碼復雜度比較高;
-
讓node支持多線程模型的模塊:threads_a_gogogithub地址:https://github.com/xk/node-threads-a-gogo(比較好用一些,參考介紹)
2、另外:異步模型的關系,導致某些代碼的執行可能先於它們所需要的條件完成之前,所以將這些需要先置條件的代碼放入到一個回調函數中,然后放入到下一個事件循環的頂層。那么這些代碼就不會被立刻執行了,而是在下一輪事件啟動之前等待,啟動后在進行執行。
范例:
var MyConstructor = function() { ... process.nextTick(function() { self._continue(); }); }; MyConstructor.prototype.__proto__ = EventEmitter.prototype; MyConstructor.prototype._continue = function() { // without the process.nextTick // these events would be emitted immediately // with no listeners. they would be lost. this.emit('data', 'hello'); this.emit('data', 'world'); this.emit('end'); }; function(req, res, next) { var c = new MyConstructor(...); c.on('data', function(data) { console.log(data); }); c.on('end', next); }