理解 Node.js 的事件輪詢

Damonare發表於2019-03-01

前言

總括

智者閱讀群書,亦閱歷人生

正文

Node.js的兩個基本概念

Node.js的第一個基本概念就是I/O操作開銷是巨大的:

理解 Node.js 的事件輪詢

所以,當前變成技術中最大的浪費來自於等待I/O操作的完成。有幾種方法可以解決效能的影響:

  • 同步方式:按次序一個一個的處理請求。:簡單;:任何一個請求都可以阻塞其他所有請求。
  • 開啟新程式:每個請求都開啟一個新程式。:簡單;:大量的連結意味著大量的程式。
  • 開啟新執行緒:每個請求都開啟一個新執行緒。:簡單,而且跟程式比,對系統核心更加友好,因為執行緒比程式輕的多;:不是所有的機器都支援執行緒,而且對於要處理共享資源的情況,多執行緒程式設計會很快變得太過於複雜。

第二個基本概念是每個連線都建立一個新執行緒是很消耗記憶體的(例如:你可以對比Nginx回想一下Apache記憶體耗盡的情景)。

Apache是多執行緒的:它為每個請求開啟一個新的執行緒(或者是程式,這取決於你的配置),當併發連線增多時,你可以看看它是怎麼一點一點耗盡記憶體的。Nginx和Node.js不是多執行緒的,因為執行緒的消耗太“重”了。它們兩個是單執行緒、基於事件的,這就把處理眾多連線所產生的執行緒/程式消耗給消除了。

單執行緒

確實只有一個執行緒:你不能並行執行任何程式碼,比如:下面的“sleep”將會阻塞sever1秒鐘:

function sleep() {
   var now = new Data().getTime();
   while (new Date().getTime() < now + 1000) {
         // do nothing
   }
}
sleep();複製程式碼

但就我目前學習階段而言,我覺得好多人對於所謂的node單執行緒是有誤解的。實際上官方給出的“單執行緒”是具有誤導性的。所謂的單執行緒是指你的程式碼只執行在一個執行緒上(好多地方都叫它主執行緒,實際上Javascript的瀏覽器執行環境不也是這麼處理我們寫的Javascript程式碼的嘛),而諸多工的並行處理,就需要多執行緒了,如下圖:

理解 Node.js 的事件輪詢

如上圖,Node.js中的單執行緒之說指的就是這個主執行緒,這個主執行緒有一個迴圈結構,保持著整個程式(你寫的程式碼)的運轉。

事件輪詢

其實上面我們所說的維持主執行緒執行的迴圈這部分就是"事件輪詢",它存在於主執行緒中,負責不停地呼叫開發者編寫的程式碼。但對開發者是不可見的。so...開發者編寫的程式碼是怎樣被呼叫的呢?看下圖:

理解 Node.js 的事件輪詢

如上圖,非同步函式在執行結束後,會在事件佇列中新增一個事件(遵循先進先出原則),主執行緒中的程式碼執行完畢後(即一次迴圈結束),下一次迴圈開始就在事件佇列中"讀取"事件,然後呼叫它所對應的回撥函式(所以回撥函式的執行順序是不一定的)。如果開發者在回撥函式中呼叫了阻塞方法(比如上文中的sleep函式),那麼整個事件輪詢就會阻塞,事件佇列中的事件得不到及時處理。正因為這樣,nodejs中的一些庫方法均是非同步的,也提倡使用者呼叫非同步方法。

var fs = require('fs');
fs.readFile('hello.txt', function (err, data) {  //非同步讀取檔案
  console.log("read file end");
});
while(1)
{
    console.log("call readFile over");
}複製程式碼

如上程式碼,我們雖然使用了非同步方法readfile讀取檔案,但read file end永遠不會輸出,因為程式碼始終在while迴圈中,下一次事件輪詢始終沒法開始,也就沒法'讀取'事件佇列呼叫相應的回撥函式了。

最後有一個Node-sample是博主平時積累的一些程式碼,包含註釋,彙總成了一個小應用,還是可以看到學習的蛛絲馬跡的。感興趣的您可以看看。

後記

參考文章:

相關文章