node異步非阻塞的雜談

引言

node做爲服務器的優點就在於適合處理高併發的請求,對於web網站後臺這種I/O密集型的後臺尤爲有優點,其核心就在於node是一個異步非阻塞模型。關於異步,同步,阻塞,非阻塞這些概念,本文不作討論。node

node的單線程模型

js做爲單線程語言,有本身的一套運行機制
圖片描述web

同步任務運行在主線程上,異步的全部任務都會在另外一個隊列中等待執行,一旦同步任務執行完畢開始執行異步隊列中的任務,此時能夠認爲將第一個異步隊列中的任務移到主線程,一旦再產生異步操做,就會繼續往異步隊列中添加,以此循環。這就是爲何promise,setTimeout,setInterval,process.nextTick,setImmediate,ajax請求,看起來雖然位於代碼的上面部分卻沒有被按順序執行。
看一下node的eventloop的機制,也就是node實現異步的架構。
圖片描述ajax

主要的不一樣點是用LIBUV去將隊列中的任務造成一個eventloop,做爲下一個循環須要執行的工做。數據庫

觀察者模式的體現

我的一直認爲js的異步回調是一種觀察者模式的體現,訂閱/發佈,網上的說法是有三種觀察者promise

idle觀察者:顧名思義,就是早已等在那裏的觀察者,之後會說到的process.nextTick就屬於這類

I/O觀察者:顧名思義,就是I/O相關觀察者,也就是I/O的回調事件,如網絡,文件,數據庫I/O等瀏覽器

check觀察者:顧名思義,就是須要檢查的觀察者,後面會說到的setTimeout/setInterval就屬於這類服務器

優先級idle觀察者>I/O觀察者>check觀察者網絡

詳細連接架構

可是我的見解setTimeout()和setInterval()能夠歸爲一類觀察者,算是timer觀察者,setImmediate()是check觀察者,至於緣由後面回說明併發

如下是本身對setTimeout(),setImmediate()和process.nextTick()的比較

首先全部討論均是創建在node的基礎上,三個函數也都只比較分析node 中狀況,摘自node文檔

setTimeout()

callback <Function> The function to call when the timer elapses.
delay <number> The number of milliseconds to wait before calling the callback.

Schedules execution of a one-time callback after delay milliseconds. Returns a Timeout for use with clearTimeout().

The callback will likely not be invoked in precisely delay milliseconds. Node.js makes no guarantees about the exact timing of when callbacks will fire, nor of their ordering. The callback will be called as close as possible to the time specified.

這裏明確了兩點,一個是settimeout的實際執行時間必然晚於設置時間,做精肯定時器根本就是違揹他的設計意願的,另一點就是他的函數回調是timer觀察的。

setImmediate()

callback <Function> The function to call at the end of this turn of the Node.js Event Loop
...args <any> Optional arguments to pass when the callback is called.

Schedules the "immediate" execution of the callback after I/O events' callbacks. Returns an Immediate for use with clearImmediate().
When multiple calls to setImmediate() are made, the callback functions are queued for execution in the order in which they are created. The entire callback queue is processed every event loop iteration. If an immediate timer is queued from inside an executing callback, that timer will not be triggered until the next event loop iteration.

核心,回調會被馬上放在eventLoop的末尾

process.nextTick()

callback <Function>
...args <any> Additional arguments to pass when invoking the callback

he process.nextTick() method adds the callback to the "next tick queue". Once the current turn of the event loop turn runs to completion, all callbacks currently in the next tick queue will be called.
This is not a simple alias to setTimeout(fn, 0). It is much more efficient. It runs before any additional I/O events (including timers) fire in subsequent ticks of the event loop.

文檔中本身就提到了process.nextTick()並不是 setTimeout(fn, 0),他更有效率,而且執行的序列必在下次全部的event loop的最前列。

比較

以後在node文檔中有一個比較細緻的比較,連接

比較這三個函數,先說process.nextTick(),文檔中說了process.nextTick() is not technically part of the event loop,如今很明確了process.nextTick()並不在event loop裏,他回調的執行是在事件等待隊列以外的,算是優先級最高的插隊人員,那它做爲最優先執行回調的就沒有疑問了,實際的用處就是有一些必須最優先執行的回調,好比網絡服務端中,端口的監聽應該必須早於其餘事件的回調。

setImmediate() vs setTimeout()

這個多是網上說法最不統一的地方了,說誰先執行的都有,先測了下面的代碼

setTimeout(() => {
 console.log('timeout');
}, 0);

setImmediate(() => {
 console.log('immediate');
});

結果還真的是隨機的,這麼不嚴謹?其實官方對這個是有解釋的

The order in which the timers are executed will vary depending on the context in which they are called. If both are called from within the main module, then timing will be bound by the performance of the process (which can be impacted by other applications running on the machine).
However, if you move the two calls within an I/O cycle, the immediate callback is always executed first:

這個教育了我,真理是有範圍的,所謂廣泛真理是形而上學。因此說setImmediate() vs setTimeout()誰快不能簡單的說,必須先討論使用的地方。

上面有提到setTimeout(fn, 0)效率不高,至於爲何,暫時參照國內廣泛的說法 該函數的事件控制,是被維護在紅黑樹上,那麼爲了每次去找超時的回調必然是logn的複雜度,而另外兩個函數看起來都應該是1的複雜度

總結

  • 綜上我的傾向於四種觀察者的說法
  • 至於setImmediate() vs setTimeout() vs process.nextTick(),process.nextTick()最快,也有獨特的應用場景。另外兩個的調用時間須要判斷是否都在主線程中被執行。
  • setTimeout(fn, 0)效率偏低。
  • tip:node官方建議使用setimmediate(),由於至少應用的範圍就能夠到瀏覽器端了。
相關文章
相關標籤/搜索