在聽到Node的介紹時,我們時常會聽到異步、非阻塞、回調、事件這些詞語混合在一起推介出來,其中異步與非阻塞聽起來似乎是同一回事。從實際效果而言,異步和非阻塞都達到了我們并行I/O的目的。但是從計算機內核I/O而言,異步/同步和阻塞/非阻塞實際上是兩回事。
操作系統內核對于I/O只有兩種方式:阻塞與非阻塞。在調用阻塞I/O時,應用程序需要等待I/O完成才返回結果,如圖:

阻塞I/O的一個特點是調用之后一定要等到系統內核層面完成所有操作后,調用才結束。以讀取磁盤上的一段文件為例,系統內核在完成磁盤尋道、讀取數據、復制數據到內存中之后,這個調用才結束。
阻塞I/O造成CPU等待I/O,浪費等待時間,CPU的處理能力不能充分利用。為了提高性能,內核提供了非阻塞I/O。非阻塞I/O跟阻塞I/O的差別為調用之后會立即返回,如圖:

操作系統對計算機進行了抽象,將所有輸入輸出設備抽象為文件。內核在進行文件I/O操作時,通過文件描述符進行管理,而文件描述符類似于應用程序與系統內核之間的憑證。應用程序如果需要進行I/O調用,需要先打開文件描述符,然后再根據文件描述符去實現文件數據的讀寫。此處非阻塞I/O與阻塞I/O的區別在于阻塞I/O完成整個獲取數據的過程,而非阻塞的I/O則不帶數據直接返回,要獲取數據,還需要通過文件描述符再次讀取。
非阻塞I/O返回之后,CPU的時間片可以用來處理其它事務,此時的性能提升是明顯的。
但非阻塞I/O也存在一些問題。由于完整的I/O并沒有完成,立即返回的不是業務層期望的數據,而僅僅是當前調用的狀態。為了獲取完整數據,應用程序需要重復調用I/O操作來確認是否完成。這種重復調用判斷操作是否完成的技術叫做輪詢,下面我們就來簡要介紹這種技術。
任意技術都并非完美的。阻塞I/O造成的CPU等待浪費,非阻塞帶來的麻煩卻是需要輪詢去確認是否完全完成數據獲取,它會讓CPU處理狀態判斷,是對CPU資源的浪費。這里我們且看輪詢技術是如何演進的,以減小I/O狀態判斷的CPU損耗。
現存的輪詢技術主要有以下這些:
**read**
它是最原始的、性能最低的一種,通過重復調用來檢查I/O的狀態來完成完整數據的讀取。在得到最終數據前,CPU一直耗用在等待上。下圖是通過read進行輪詢的示意圖:

**select**
它是在read基礎上改進的一種方案,通過對文件描述符上的事件狀態來進行判斷。通過select進行輪詢示意圖:

select輪詢具有一個較弱的限制,那就是由于它采用了一個1024長度的數組來存儲狀態,所以它最多可以同時檢查1024個文件描述符。
**poll**
該方案較select有所改進,采用鏈表的方式避免數組長度的限制,其次它能避免不需要的檢查。但是當文件描述符較多的時候,它的性能還是十分低下的。通過poll實現輪詢的示意圖,它和select相似,但是性能有所改進:

**epoll**
該方案是Linux下效率最高的I/O事件通知機制,在進入輪詢的時候如果沒有檢查到I/O事件,將會進行休眠,直到事件發生將它喚醒。它是真實利用了事件通知、執行回調的方式,而不是遍歷查詢,所以不會浪費CPU,執行效率較高。下圖為通過epoll方式實現輪詢示意圖:

**kqueue**
該方案的實現方式與epoll類似,不過它僅在FreeBSD系統下存在。
輪詢技術滿足了非阻塞I/O確保獲取完整數據的要求,但是對于應用程序而言,它仍然只能算是一種同步,因為應用程序仍然需要等待I/O完全返回,依舊花費了很多時間來等待。等待期間,CPU要么用于遍歷文件描述符的狀態,要么休眠等待事件發生。結論就是它不夠好。
- 目錄
- 第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 事件驅動與高性能服務器