node 中的異步

Node在*nix平臺下采用了libeio配合libev實現I/O部分,實現了異步I/O
在Node v0.9.3中,自行實現了線程池來完成異步I/O
libeio 實質上依然是採用線程池與阻塞I/O模擬異步I/O
Node在Windows下采用IOCP來實現異步I/O
在這裏插入圖片描述
完成整個異步I/O環節的有事件循環、觀察者 和 請求對象等。

事件循環 

白話講解:

這個過程就如同飯館的廚房,廚房一輪一輪地製作菜餚,但是要具體制作哪些菜餚取決於收 銀臺收到的客人的下單。廚房每做完一輪菜餚,就去問收銀臺的小妹,接下來有沒有要做的菜, 如果沒有的話,就下班打烊了。在這個過程中,收銀臺的小妹就是觀察者,她收到的客人點單就 是關聯的回調函數。當然,如果飯館經營有方,它可能有多個收銀員,就如同事件循環中有多個 觀察者一樣。收到下單就是一個事件,一個觀察者裏可能有多個事件。

在進程啓動時,Node便會創建一個類似於while(true)的循環,每執行一次循環體的過程我 們稱爲Tick。每個Tick的過程就是查看是否有事件待處理,如果有,就取出事件及其相關的回調 函數。如果存在關聯的回調函數,就執行它們。然後進入下個循環,如果不再有事件處理,就退 出進程。流程圖如圖
在這裏插入圖片描述
觀察者
在每個Tick的過程中,如何判斷是否有事件需要處理呢?這裏必須要引入的概念是觀察者
每個事件循環中有一個或者多個觀察者,而判斷是否有事件要處理的過程就是向這些觀察者詢問 是否有要處理的事件。
瀏覽器採用了類似的機制。事件可能來自用戶的點擊或者加載某些文件時產生,而這些產生 的事件都有對應的觀察者
在Node中,事件主要來源於網絡請求、文件I/O等,這些事件對應的 觀察者有文件I/O觀察者、網絡I/O觀察者等。觀察者將事件進行了分類

事件循環是一個典型的生產者/消費者模型

異步I/O、網絡請求等則是事件的生產者,源源 不斷爲Node提供不同類型的事件,這些事件被傳遞到對應的觀察者那裏,事件循環則從觀察者那 裏取出事件並處理
Windows下,這個循環基於IOCP創建,而在*nix下則基於多線程創建

整個異步I/O的流程圖如下
在這裏插入圖片描述事件循環、觀察者、請求對象、I/O線程池這四者共同構成了Node異步I/O模型的基本要素。

事實上,在Node中, 除了JavaScript是單線程外,Node自身其實是多線程的

非 I/O 的異步 API

儘管我們在介紹Node的時候,多數情況下都會提到異步I/O,但是Node中其實還存在一些與 I/O無關的異步API,這一部分也值得略微關注一下,它們分別是setTimeout()、setInterval()、 setImmediate()和process.nextTick()
setTimeout()和setInterval()與瀏覽器中的API是一致的,分別用於單次和多次定時執行任 務。它們的實現原理與異步I/O比較類似,只是不需要I/O線程池的參與。調用setTimeout()或者 setInterval()創建的定時器會被插入到定時器觀察者內部的一個紅黑樹中。。每次Tick執行時,會從該紅黑樹中迭代取出定時器對象,檢查是否超過定時時間,如果超過,就形成一個事件,它的 回調函數將立即執行

在這裏插入圖片描述
process.nextTick()
每次調用process.nextTick()方法,只會將回調函數放入隊列中,在下一輪Tick時取出執行。 定時器中採用紅黑樹的操作時間複雜度爲O(lg(n)),nextTick()的時間複雜度爲O(1)。相較之下, process.nextTick()更高效。
setImmediate()
setImmediate()方法與process.nextTick()方法十分類似,都是將回調函數延遲執行。
但是兩者之間其實是有細微差別的。將它們放在一起時,又會是怎樣的優先級呢。示例代碼 如下:

setImmediate(function () {
    console.log('setImmediate延遲執行'); 
});

process.nextTick(function () { 
    console.log('nextTick延遲執行');
});

console.log('正常執行');
//正常執行
//nextTick延遲執行
//setImmediate延遲執行

從結果裏可以看到,process.nextTick()中的回調函數執行的優先級要高於setImmediate()。 這裏的原因在於事件循環對觀察者的檢查是有先後順序的,process.nextTick()屬於idle觀察者, setImmediate()屬於check觀察者。在每一個輪循環檢查中:

idle觀察者 先於 I/O觀察者 先於 check觀察者

在具體實現上,process.nextTick()的回調函數保存在一個數組中,setImmediate()的結果 則是保存在鏈表中

  • process.nextTick : 數組中
  • setImmediate: 鏈表中

主模塊內調用(通常情況下):
process.nextTick 先於 setTimeout 先於 setImmediate 先於 I/O
I/O操作中
process.nextTick 先於setImmediate 先於 setTimeout 先於 I/O

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章