在咱們寫前端代碼的時候,常常會遇到的異步的一些操做,好比ajax
,setTimeout
,瀏覽器事件
等,雖然js執行是單線程的,可是瀏覽器倒是有多個線程的,例如發起ajax
請求,瀏覽器會另起一個http
線程。Node相似,js執行爲單線程,可是遇到一些I/O操做,提供了異步的方式。javascript
關於同步和異步,請移步我以前寫的一篇文章.前端
這篇文章算是對於《深刻淺出Node.js》第三章內容的一個摘錄和小結:java
在瞭解Node.js中
的異步I/O前,先了解下關於阻塞和非阻塞的概念:ajax
阻塞I/O:在Node.js執行過程當中,若是遇到了磁盤的讀寫或網絡請求時,通常可能會耗費比較多的處理時間,這個時候操做系統會剝奪Node.js這個線程的控制權,這時Node.js便中止了執行,等待系統內核層面完成全部的操做,當I/O操做
完成後,操做系統會再將CPU的控制權返還給Node.js這個線程,而後Node.js繼續向後執行代碼。這個即是阻塞的I/O
;segmentfault
非阻塞I/O:當Node.js遇到耗時的I/O時,會將這個I/O的請求交給操做系統,操做系統會立馬返回當前調用的狀態,此時Node.js會繼續向下執行代碼,不會發生等待的狀況,可是Node.js會重複調用I/O操做來確認是否完成,即輪詢。windows
首先得清楚,Node自身執行的模型----事件循環。在進程啓動時,Node便會建立一個相似於while(true)
的循環,每執行一次循環體的過程咱們稱爲Tick
。每一個Tick
的過程就是查看是否有事件待處理,若是有,就去除事件及其相關的回調函數。瀏覽器
異步I/O
算是Node的特點,它力求在單線程上將資源分配得更加高效。異步I/O
的提出主要是爲了知足單線程執行的js,當進行耗時的I/O操做
的時候,將原有等待I/O完成
的時間分配給其餘須要的業務去執行。網絡
每一個事件循環中有一個或者多個觀察者,而判斷是否有事件要處理的過程就是向這些觀察者詢問是否有要處理的事件。事件循環是一個典型的生產者/消費者模型
。異步I/O、網絡請求等都是事件的生產者,源源不斷爲Node提供不一樣類型的事件,這些事件被傳遞到對應的觀察者那裏,事件循環則從觀察者那裏取出事件並處理。
在Windows
下,這個循環基於IOCP建立
,而在*nix
下則基於多線程建立
。多線程
在通常的(非異步)的回調函數的調用中,函數能夠由咱們自行去調用:異步
var forEach = function(list, callback) { for(var i = 0; i < list.length; i++) { callback(list[i], i, list); } }
可是在Node的異步I/O
過程中,回調函數並不禁開發者來調用。瀏覽器中的異步事件同理,具體內容請戳我。在js發起調用到內核完成I/O操做
的過渡過程中,存在一種中間產物,叫作請求對象
。
例如Node.js提供的核心fs模塊,要去打開一個文件,fs.open(path, flags, mode, callback)
。js調用Node.js的核心模塊,核心模塊去調用C++核心模塊去進行下層的操做。再調用底層代碼時,建立了一個請求對象,從js層面傳入的參數和callback
都被封裝在這個請求對象上。其中callback
被設置在這個對象的一個屬性上。
如下內容是在windows
環境下Node.js異步I/O模型
。
在windows
下,這個對象被推入線程池中等待執行。此時,js調用當即返回,由js層面發起的異步調用的第一個階段就此結束。Node.js再次得到了CPU
的使用權,Node.js能夠繼續執行當前任務的後續操做。當前I/O操做
在線程池中等待執行,無論它是否阻塞I/O
,都不會影響到js線程後續執行,如此就達到了異步的目的。
請求對象
是異步I/O
過程當中的重要中間產物,全部的狀態都保存在這個對象中,包括送去線程池等待執行以及I/O操做完畢後的回調處理。
線程池中的I/O操做
調用完畢以後,在每次tick
執行過程當中,會調用IOCP
提供的相關方法檢查線程池中是否有執行完的請求,若是會將請求對象加入到I/O觀察者隊列
中,而後將其當作事件處理。
Windows
下主要經過IOCP
來向系統內核發送I/O調用
和從內核獲取已完成的I/O操做
,配以事件循環,以此完成異步I/O
的過程。在Linux
下經過epoll
實現這個過程,FreeBSD
下經過kqueue
實現,Solaris
下經過Event ports
實現。不一樣的是線程池在windows下由內核(IOCP)直接提供,*nix系列下有libuv執行實現。