1. 程式人生 > 其它 >nodejs 非同步 I/O 和事件驅動

nodejs 非同步 I/O 和事件驅動

非同步IO(asynchronous I/O)

阻塞I/O 和 非阻塞I/O

阻塞I/O,就是當用戶發一個讀取檔案描述符的操作的時候,程序就會被阻塞,直到要讀取的資料全部準備好返回給使用者,這時候程序才會解除block的狀態。

非阻塞I/O,就與上面的情況相反,使用者發起一個讀取檔案描述符操作的時,函式立即返回,不作任何等待,程序繼續執行。但是程式如何知道要讀取的資料已經準備好了呢?最簡單的方法就是輪詢。

除此之外,還有一種叫做IO多路複用的模式,就是用一個阻塞函式同時監聽多個檔案描述符,當其中有一個檔案描述符準備好了,就馬上返回,在linux下,select,poll,epoll都提供了IO多路複用的功能。

同步I/O 和 非同步I/O

那麼同步I/O和非同步I/O又有什麼區別麼?是不是隻要做到非阻塞IO就可以實現非同步I/O呢?

其實不然。

同步I/O(synchronous I/O)做I/O operation的時候會將process阻塞,所以阻塞I/O,非阻塞I/O,IO多路複用I/O都是同步I/O。

非同步I/O(asynchronous I/O)做I/O opertaion的時候將不會造成任何的阻塞。

非阻塞I/O都不阻塞了為什麼不是非同步I/O呢?前端培訓其實當非阻塞I/O準備好資料以後還是要阻塞住程序去核心拿資料的。所以算不上非同步I/O。

這裡借一張圖(圖來自這裡)來說明他們之間的區別

事件驅動

事件驅動(event-driven)是nodejs中的第二大特性,就是通過監聽事件的狀態變化來做出相應的操作。比如讀取一個檔案,檔案讀取完畢,或者檔案讀取錯誤,那麼就觸發對應的狀態,然後呼叫對應的回掉函式來進行處理。

執行緒驅動和事件驅動

執行緒驅動程式設計和事件驅動程式設計之間的區別:

執行緒驅動就是當收到一個請求的時候,將會為該請求開一個新的執行緒來處理請求。一般存在一個執行緒池,執行緒池中有空閒的執行緒,會從執行緒池中拿取執行緒來進行處理,如果執行緒池中沒有空閒的執行緒,新來的請求將會進入佇列排隊,直到執行緒池中空閒執行緒。

事件驅動就是當進來一個新的請求的時,請求將會被壓入佇列中,然後通過一個迴圈來檢測佇列中的事件狀態變化,如果檢測到有狀態變化的事件,那麼就執行該事件對應的處理程式碼,一般都是回撥函式。

對於事件驅動程式設計來說,如果某個時間的回撥函式是計算密集型,或者是阻塞I/O,那麼這個回撥函式將會阻塞後面所有事件回撥函式的執行。這一點尤為重要。

nodejs的事件驅動和非同步I/O

事件驅動模型

nodejs是單執行緒(single thread)執行的,通過一個事件迴圈(event-loop)來迴圈取出訊息佇列(event-queue)中的訊息進行處理,處理過程基本上就是去呼叫該訊息對應的回撥函式。訊息佇列就是當一個事件狀態發生變化時,就將一個訊息壓入佇列中。

nodejs的時間驅動模型一般要注意下面幾個點:

1.因為是單執行緒的,所以當順序執行js檔案中的程式碼的時候,事件迴圈是被暫停的。

2.當js檔案執行完以後,事件迴圈開始執行,並從訊息佇列中取出訊息,開始執行回撥函式

3.因為是單執行緒的,所以當回撥函式被執行的時候,事件迴圈是被暫停的

4.當涉及到I/O操作的時候,nodejs會開一個獨立的執行緒來進行非同步I/O操作,操作結束以後將訊息壓入訊息佇列。

不多說,看例子

var fs = require("fs");var debug = require('debug')('example1');

debug("begin");

fs.readFile('package.json','utf-8',function(err,data){

    if(err)  

        debug(err);

    else

        debug("get file content");

});

setTimeout(function(){

    debug("timeout2");

});

debug('end'); // 執行到這裡之前,事件迴圈是暫停的
  1. 同步執行debug("begin")
  2. 非同步呼叫fs.readFile(),此時會開一個新的執行緒去進行非同步I/O操作
  3. 非同步呼叫setTimeout(),馬上將超時資訊壓入到訊息佇列中
  4. 同步呼叫debug("end")
  5. 開啟事件迴圈,彈出訊息佇列中的資訊(目前是超時資訊)
  6. 然後執行資訊對應的回撥函式(事件迴圈又被暫停)
  7. 回撥函式執行結束後,開始事件迴圈(目前訊息佇列中沒有任何東西,檔案還沒讀完)
  8. 非同步I/O讀取檔案完畢,將訊息壓入訊息佇列(訊息中含有檔案內容或者是出錯資訊)
  9. 事件迴圈取得訊息,執行回撥
  10. 程式退出。

這裡借一張圖來說明nodejs的事件驅動模型

這裡最後要說的一點就是如何手動將一個函式推入佇列,nodejs為我們提供了幾個比較方便的方法:

setTimeout()

process.nextTick()

setImmediate()

非同步I/O

nodejs中的非同步I/O的操作是通過libuv這個庫來實現的,包含了window和linux下面的非同步I/O實現。