每當咱們提起 node.js 時總會脫口而出 事件驅動
、非阻塞I/O
和 單線程
,因此我總結了如下幾點對這三項概念的闡述,不必定正確僅僅表明我的觀點。php
當一個應用程序運行時會產生一個主進程,它與其餘並行執行的應用程序一塊兒竟爭計算機系統資源,是管理和分配現有所佔據資源的基本單位。每個進程都有一個本身的地址空間(進程空間)。而線程是進程的一部分,兩者相扶相依,其中單線程被稱爲輕權進程或輕量級進程,執行特性:html
線程只有 3 個基本狀態:就緒,執行,阻塞。node
線程存在 5 種基本操做來切換線程的狀態:派生,阻塞,激活,調度,結束。nginx
下面這個圖片簡單解釋了單線程的運做:apache
用 php、apache、nginx 和 node.js 作對比是由於這四個應用剛好表明四種狀況,其中 php 是典型的多線程的語言,apache 則是同步多進程的表明,也就是說鏈接每個鏈接 apache 都對應一個子進程,而 nginx 是異步多線程,近萬個鏈接對應一個子進程。編程
當咱們說 node.js 是單線程應用的時候,實際上這種單線程只是代碼執行主程序上的單線程,在涉及到 IO 操做時仍然是多線程,下面咱們看一段代碼:多線程
var path = require('path'), fs = require('fs'); var i = 0; console.time('fs.read'); fs.read(fs.openSync(path.join(__dirname, 'example.log'), 'r'), 10000, 0, 'utf-8', function () { console.log('1'); console.timeEnd('fs.read'); }); console.log('2'); function test(cb) { console.time('test'); console.log('3'); while (i < 300000000) { i++ } cb() } console.log('4'); test(function () { console.log('5'); console.timeEnd('test'); }); console.log('6'); //process.exit();
運行結果時:異步
數字標識分別爲二、四、三、五、六、1,fs
和 test
一樣是回調函數一個當即執行並順序執行,而另外一個延遲執行,這顯然不符合傳統的程序執行順序,也不符合有些人說的: 這種以定義當前感興趣事件發生時由系統調用的函數來取代應用返回值的編程風格被稱爲事件驅動編程或者異步編程
,這句話的錯誤在因而事件驅動編程
但不必定是異步編程
,異步編程
的先決條件是當前執行函數正在進行 IO
操做。異步編程
若是咱們把上述代碼的最後一句 //process.exit();
的註釋拿掉,能夠看到執行結果:函數
這種狀況是由於 fs
的 IO 操做是異步
,而且執行結果在 事件驅動編程
的 事件隊列
的最低端,而在它以前 process.exit();
已經在 事件循環
過程當中被從事件隊列中取出放入調用堆棧,結束了主進程。因此發生 fs
的回調結果沒有顯示,由於它已經被放在了整段代碼執行環境中的 事件隊列
的最下方(這裏就是非阻塞的實例)。
以上所述證實了 IO 操做與其餘函數的這種區別是由 libeio 實現,libeio 是用多線程的方式,在標準的阻塞式IO上模擬非阻塞異步,線程池默認限制四線程。
另外的 libev 事件可獲得 IO 執行狀態。Node.js 的開發者在 libev 和 libeio 的基礎上還抽象出了 libuv 層: (http://docs.libuv.org/en/v1.x/design.html)。
全部的 IO操做都會轉發給由 libuv 管理的工做線程去執行,由 libuv 與 libev 和 libeio 進行交互。
事件驅動與事件循環互爲犄角,其中事件循環具有兩個功能:
事件檢測
事件觸發處理