NodeJs 非同步 I/O

admin發表於2020-06-17

目前的開發過程中,都是單執行緒的,所以當兩個請求發起的時候,一個在請求,另一個就必須等待上一個請求完成後才能開始自己的請求,這樣就大大的浪費了時間,而且在使用者體驗上也非常糟糕。


NodeJs的非同步I/O解決了什麼問題:

  1. 使用者體驗

  2. 資源分配

  3. 非同步I/O實現現狀

  4. 非同步I/O與非阻塞I/O

  5. 理想的非阻塞非同步I/O

  6. 事實的非同步的I/O

Node的非同步I/O:

  1. 事件迴圈

  2. 觀察者

  3. 請求物件

  4. 執行回撥

Node的事件迴圈:

在程式啟動時,Node便會建立一個類似於while(true)的迴圈,每執行一次迴圈體的過程稱為Tick。每個Tick的過程就是檢視是否有事件待處理,如果有,就取出事件及其相關的回撥函式。如果存在關聯的回撥函式,就執行它們。然後進入下個迴圈,如果不再有事件處理,就退出程式。

Node的觀察者:

在每個Tick的過程中,如何判斷是否有事件需要處理呢?這裡必須要引入的概念是[觀察者]。每個事件迴圈中有一個或者多個觀察者,而判斷是否有事件要處理的過程就是向這些觀察者詢問是否有要處理的事件。


事件可能來自使用者的點選或者載入某些檔案時產生,而這些產生的事件都有對應的觀察者。在Node中,事件主要來源於網路請求、檔案I/O等,這些事件對應的觀察者有檔案I/O觀察者、網路I/O觀察者等。觀察者將事件進行了分類。


事件迴圈是一個典型的生產者/消費者模型。非同步I/O、網路請求等則是事件的生產者,源源不斷為Node提供不同型別的事件,這些事件被傳遞到對應的觀察者那裡,事件迴圈則從觀察者那裡取出事件並處理。

請求物件:

對於一般的(非非同步)回撥函式,函式由我們自行呼叫,而對於Node中的非同步I/O呼叫而言,回撥函式卻不由開發者來呼叫。那麼從我們呼叫後,到回撥函式被執行,中間發生了什麼呢?事實上,從JavaScript發起呼叫到核心執行完I/O操作的過渡過程中,存在一種中間產物,它叫做請求物件。

執行回撥:

組裝好請求物件、送入I/O執行緒池等待執行,實際上完成了非同步I/O的第一部分,回撥通知是第二部分。

執行緒池中的I/O操作呼叫完畢後,會將獲取的結果儲存在req->result屬性上,然後呼叫PostQueuedCompletionStatus()通知IOCP,告知當前物件操作已經完成:

[JavaScript] 純文字檢視 複製程式碼
PostQueuedCompletionStatus((loop)->iocp,0,0,&((req)->overlapped))

PostQueuedCompletionStatus()方法的作用是向IOCP提交執行狀態,並將執行緒歸還執行緒池。通過PostQueuedCompletionStatus()方法提交的狀態,可以通過GetQueuedCompletionStatus()提取。


在這個過程中,我們其實還動用了事件迴圈的I/O觀察者。在每次Tick的執行中,它會呼叫將請求物件加入到dI/O觀察者的佇列中,然後將其當做事件處理。


I/O觀察者回撥函式的行為就是取出請求物件的result屬性作為引數,取出oncomplete_sym屬性作為方法,然後呼叫執行,以此達到呼叫JavaScript中傳入的回撥函式的目的。至此,整個非同步I/O的流程完全結束。

總結:

事件迴圈、觀察者、請求物件、I/O執行緒池這四者共同構成了Node非同步I/O模型的基本要素。

相關文章