神奇的nextTick


在nodejs中,process.nextTick()可以說是個繞不開的地方,不然你都不好意思跟別人說你已經懂了nodejs的異步非阻塞特性了。

簡介

首先開聽聽nodejs中對nextTick的解釋:

On the next loop around the event loop call this callback. This is not a simple alias to setTimeout(fn, 0), it's much more efficient

可以清楚得看到,nextTick()達到的效果跟setTimeout(fn, 0)是一樣,但為什么nextTick()會比setTimeout()更高效,不知道大家有沒有想多這樣的問題。

回答這個問題之前,大家要搞清楚nodejs中Tick的概念:在libev中的event loop 的每次迭代,在nodejs 中就叫做 “Tick”。javascript是單進程運行的,具體到nodejs中,就是如圖運行。

 

如果用nextTick()話,它的執行時間是O(1), 而setTimeout(fn, 0)則是O(n),考慮到篇幅,這里不再做具體的詳述,有興趣的朋友可以看《nodejs 異步之 Timer &Tick; 篇》一文,里面做了具體的解釋。

 

應用場景

通過之前的介紹,可以看到nextTick()就是將某個任務放到下一個Tick中執行,簡單講就是延遲執行,那具體什么場景下需要用到這個功能呢?

一. 同步emit時間,保證時序

比如以下這段代碼:

var EventEmitter = require('events').EventEmitter;

function StreamLibrary(resourceName) { 
    this.emit('start');
    // read from the file, and for every chunk read, do: 
       this.emit('data', chunkRead); 
}
StreamLibrary.prototype.__proto__ = EventEmitter.prototype;

var stream = new StreamLibrary('fooResource');
stream.on('start', function() {
    console.log('Reading has started');
});

stream.on('data', function(chunk) {
    console.log('Received: ' + chunk);
});

從代碼本身來看沒什么問題,但事實是:我們可能永遠也監聽不到"start"事件,因為這個事件在調用構造函數時,已經馬上拋出了這個事件。

那解決方案就是讓'start'延時執行,具體如下:

function StreamLibrary(resourceName) { 
   var self = this;
   process.nextTick(function() {
     self.emit('start');
});

   // read from the file, and for every chunk read, do: 
       this.emit('data', chunkRead); 
}

 

其實早nodejs的源碼也存在大量這樣的用法,比如net模塊

    require('dns').lookup(host, function(err, ip, addressType) {
      // It's possible we were destroyed while looking this up.
      // XXX it would be great if we could cancel the promise returned by
      // the look up.
      if (!self._connecting) return;

      if (err) {
        // net.createConnection() creates a net.Socket object and
        // immediately calls net.Socket.connect() on it (that's us).
        // There are no event listeners registered yet so defer the
        // error event to the next tick.
        process.nextTick(function() {
          self.emit('error', err);
          self._destroy();
        });
      }

這里的err處理就是出於這樣的目的。

具體代碼連接:https://github.com/joyent/node/blob/master/lib/net.js#L806

 

二. cpu高密集代碼段

由於javascript是單進程的,當一個cpu被一個任務任務跑滿時,它是不能執行其他任務的,在web服務中,這點甚至是致命,如果它被一個cpu密集的任務占滿,那就不能相應其他的請求了,程序相當於“假死”。

這個時候,將這個任務推遲執行倒不失為一個不錯的選擇。

比如:

var http = require('http');

function compute() {
   // 執行一個cpu密集的任務
   // ...
   process.nextTick(compute);
}

http.createServer(function(req, res) {
   res.writeHead(200, {'Content-Type': 'text/plain'});
   res.end('Hello World');
}).listen(5000, '127.0.0.1');

compute();

 

但問題又來,我們怎么去界定哪些任務是需要推遲推遲執行,而那些則不需要,有什么量化的標准嗎?

對不起,我暫時也沒有特別好的方法。執行時間是個很好的判斷標准,到底是1毫秒,1秒,還是10秒.......,並有很好的量化標准

所以我的感受是:在你懂得nextTick的原理后,根據自己的業務場景來決定。

 

經典文章:
Understanding process.nextTick()
nextTick and setTimeout
test case about nextTick and setTimeout


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM