在這一節中,我們將通過解釋Windows下異步I/O(利用IOCP實現)的簡單例子來探尋從JavaScript代碼到系統內核之間都發生了什么。
對于一般的(非異步)回調函數,函數由我們自行調用,如下所示:
~~~
var forEach = function(list, callback){
for(var i=0;i<list.length;i++){
callback(list[i],i,list);
}
};
~~~
對于Node中的異步I/O調用而言,回調函數卻不由開發者來調用。那么從我們發出調用后,到回調函數被執行,中間發生了什么呢?事實上,從JavaScript發起調用到內核執行完I/O操作的過渡過程中,存在一種中間產物,它叫做請求對象。
下面我們以最簡單的fs.open()方法來作例子,探索Node與底層之間是如何執行異步I/O調用以及回調函數究竟是如何被調用執行的:
~~~
fs.open=function(path, flags, mode, callback){
//...
binding.open(pathModule._makeLong(path),
stringToFlags(flags),
mode,
callback);
};
~~~
fs.open()的作用是根據指定路徑和參數去打開一個文件,從而得到一個文件描述符,這是后續所有I/O操作的初始操作。從前面的代碼中可以看到,JavaScript層面的代碼通過調用C++核心模塊進行下層的操作。下圖為調用示意圖:

從JavaScript調用Node的核心模塊,核心模塊調用C++內建模塊,內建模塊通過libuv進行系統調用,這是Node里經典的調用方式。這里libuv作為封裝層,有兩個平臺實現,實質上是調用了uv_fs_open()方法。在uv_fs_open()的調用過程中,我們創建了一個FSReqWrap請求對象。從JavaScript層傳入的參數和當前方法都被封裝在這個請求對象中,其中我們最為關注的回調函數則被設置在這個對象的oncomplete_sym屬性上:
~~~
req_wrap->object->Set(oncomplete_sym, callback);
~~~
對象包裝完畢后,在Windows下,則調用QueueUserWorkItem()方法將這個FSReqWrap對象推入線程池中等待執行,該方法的代碼如下所示:
~~~
QueueUserWorkItem(&uv_fs_thread_proc, \
req, \
WT_EXECUTEDEFAULT)
~~~
QueueUserWorkItem()方法接受3個參數:第一個參數是將要執行的方法的引用,這里引用的是uv_fs_thread_proc,第二個參數是uv_fs_thread_proc方法運行時所需要的參數;第三個參數是執行的標志。當線程池中有可用線程時,我們會調用uv_fs_thread_proc()方法。uv_fs_thread_proc()方法會根據傳入的參數的類型調用相應的底層函數。以uv_fs_open()為例,實際上調用fs_open()方法。
至此,JavaScript調用立即返回,由JavaScript層面發起的異步調用的第一階段就此結束。JavaScript線程可以繼續執行當前任務的后續操作。當前的I/O操作在線程池中等待執行,不管它是否阻塞I/O,都不會影響到JavaScript線程的后續執行,如此就達到了異步的目的。
請求對象是異步I/O過程中的重要中間產物,所有的狀態都保存在這個對象中,包括送入線程池等待執行以及I/O操作完畢后的回調處理。
- 目錄
- 第1章 Node 簡介
- 1.1 Node 的誕生歷程
- 1.2 Node 的命名與起源
- 1.2.1 為什么是 JavaScript
- 1.2.2 為什么叫 Node
- 1.3 Node給JavaScript帶來的意義
- 1.4 Node 的特點
- 1.4.1 異步 I/O
- 1.4.2 事件與回調函數
- 1.4.3 單線程
- 1.4.4 跨平臺
- 1.5 Node 的應用場景
- 1.5.1 I/O 密集型
- 1.5.2 是否不擅長CPU密集型業務
- 1.5.3 與遺留系統和平共處
- 1.5.4 分布式應用
- 1.6 Node 的使用者
- 1.7 參考資源
- 第2章 模塊機制
- 2.1 CommonJS 規范
- 2.1.1 CommonJS 的出發點
- 2.1.2 CommonJS 的模塊規范
- 2.2 Node 的模塊實現
- 2.2.1 優先從緩存加載
- 2.2.2 路徑分析和文件定位
- 2.2.3 模塊編譯
- 2.3 核心模塊
- 2.3.1 JavaScript核心模塊的編譯過程
- 2.3.2 C/C++核心模塊的編譯過程
- 2.3.3 核心模塊的引入流程
- 2.3.4 編寫核心模塊
- 2.4 C/C++擴展模塊
- 2.4.1 前提條件
- 2.4.2 C/C++擴展模塊的編寫
- 2.4.3 C/C++擴展模塊的編譯
- 2.4.2 C/C++擴展模塊的加載
- 2.5 模塊調用棧
- 2.6 包與NPM
- 2.6.1 包結構
- 2.6.2 包描述文件與NPM
- 2.6.3 NPM常用功能
- 2.6.4 局域NPM
- 2.6.5 NPM潛在問題
- 2.7 前后端共用模塊
- 2.7.1 模塊的側重點
- 2.7.2 AMD規范
- 2.7.3 CMD規范
- 2.7.4 兼容多種模塊規范
- 2.8 總結
- 2.9 參考資源
- 第3章 異步I/O
- 3.1 為什么要異步I/O
- 3.1.1 用戶體驗
- 3.1.2 資源分配
- 3.2 異步I/O實現現狀
- 3.2.1 異步I/O與非阻塞I/O
- 3.2.2 理想的非阻塞異步I/O
- 3.2.3 現實的異步I/O
- 3.3 Node的異步I/O
- 3.3.1 事件循環
- 3.3.2 觀察者
- 3.3.3 請求對象
- 3.3.4 執行回調
- 3.3.5 小結
- 3.4 非I/O的異步API
- 3.4.1 定時器
- 3.5 事件驅動與高性能服務器