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'); // 執行到這裡之前,事件迴圈是暫停的
- 同步執行debug("begin")
- 非同步呼叫fs.readFile(),此時會開一個新的執行緒去進行非同步I/O操作
- 非同步呼叫setTimeout(),馬上將超時資訊壓入到訊息佇列中
- 同步呼叫debug("end")
- 開啟事件迴圈,彈出訊息佇列中的資訊(目前是超時資訊)
- 然後執行資訊對應的回撥函式(事件迴圈又被暫停)
- 回撥函式執行結束後,開始事件迴圈(目前訊息佇列中沒有任何東西,檔案還沒讀完)
- 非同步I/O讀取檔案完畢,將訊息壓入訊息佇列(訊息中含有檔案內容或者是出錯資訊)
- 事件迴圈取得訊息,執行回撥
- 程式退出。
這裡借一張圖來說明nodejs的事件驅動模型
這裡最後要說的一點就是如何手動將一個函式推入佇列,nodejs為我們提供了幾個比較方便的方法:
setTimeout()
process.nextTick()
setImmediate()
非同步I/O
nodejs中的非同步I/O的操作是通過libuv這個庫來實現的,包含了window和linux下面的非同步I/O實現。