nodejs事件輪詢詳述

2022-01-10 22:54:02 字數 3616 閱讀 5052

目錄

概述

關於nodejs的介紹網上資料非常多,最近由於在整理一些函式式程式設計的資料時,多次遇到nodejs有關的內容。所以就打算專門寫一篇文章總結一下nodejs相關知識,包括「說它單執行緒是什麼意思」、「非阻塞又是指什麼」以及最重要的是它的「事件輪詢」的實現機制。

本文不介紹nodejs的優缺點(適用場合)、nodejs環境怎樣搭建以及一些nodejs庫的使用等等這些基礎知識。

nodejs特點

網上任何一篇關於nodejs的介紹中均會提及到nodejs兩個主要特點:單執行緒、非阻塞。但是據我所了解到的,大部分介紹一帶而過,並沒有詳細地、系統性地去說明它們到底是怎麼回事。下面我依次盡我所能詳細地說一下我對以上兩者的理解。

非阻塞

我們先來看一段.net中非同步程式設計的**:

using(filestream fs = new filestream("hello.txt", filemode.open))
如上**所示,由於filestream.beginread是乙個非同步方法,所以不管hello.txt檔案有多大,filestream.beginread方法的呼叫並不會阻塞呼叫執行緒,console.writeline方法立馬便可執行。同理,如果在nodejs中所有的方法都是「非同步方法」,那麼在nodejs中任何方法的呼叫均不會阻塞呼叫執行緒,實質上,nodejs中大部分庫方法確實是這樣的。這就是為什麼我們會說nodejs中**是非阻塞的。

單執行緒

對這個概念有誤解的人非常之多,以為nodejs程式中就乙個執行緒,然後有很多人會問:既然只有乙個執行緒,那麼怎麼並行處理多個任務呢?

其實這裡說的單執行緒並不是指nodejs程式中只有乙個執行緒存在,我個人感覺官方給出「單執行緒」說法本身就具有誤導性,所以也怪不得大部分初學者。那麼「單執行緒」到底什麼意思呢?其實這裡的「單執行緒」指的是我們(開發者)編寫的**只能執行在乙個執行緒當中(可以稱之為主線程吧),就像我們在windows桌面程式開發中一樣,編寫的所有介面**均執行在ui執行緒之中。

那麼還是剛才那個問題,所有編寫的**均執行在乙個執行緒中,那麼怎樣去並行處理任務呢?這個就要想到前面介紹的「非同步方法」了,沒錯,雖然開發者編寫的所有**均執行在乙個執行緒中,但是我們可以在這個執行緒中呼叫非同步方法啊,而非同步方法內部實現過程當然要採用多執行緒了。就像下圖:

如上圖所示,nodejs中的單執行緒指的是圖中的主線程,該主線程中包含乙個迴圈結構,維持整個程式持續運轉。

注:該迴圈結構也稱之為「幫浦」結構,是每個系統必備的結構。具體可以參見我之前的一篇部落格《動力之源:**中的幫浦》。

因此我們可以說,在nodejs中寫的**(包括**方法)均只執行在乙個執行緒中,但是不代表它只有乙個執行緒。nodejs中許多非同步方法在具體的實現時,內部均採用了多執行緒機制(具體後面會講到)。

事件輪詢

如果看過我前面部落格的一些讀者可能知道,乙個系統(或者說乙個程式)中必須至少包含乙個大的迴圈結構(我稱之為「幫浦」),它是維持系統持續執行的前提。nodejs中一樣包含這樣的結構,我們叫它「事件輪詢」,它存在於主線程中,負責不停地呼叫開發者編寫的**。我們可以檢視nodejs官方**上對nodejs的說明:

我們可以看到,在nodejs中這個「迴圈」結構對開發者來講是不可見的。

那麼開發者編寫的**是怎樣通過事件輪詢來得到呼叫的呢?尤其是一些非同步方法中帶的**函式?看下面一張圖:

如上圖所示,每個非同步函式執行結束後,都會在事件佇列中追加乙個事件(同時儲存一些必要引數)。事件輪詢下一次迴圈便可取出事件,然後會呼叫非同步方法對應的**函式(引數)。這樣一來,nodejs便能保證開發者編寫的每行**(每個**)均在主線程中執行。注意這裡有乙個問題,如果開發者在**函式中呼叫了阻塞方法,那麼整個事件輪詢就會阻塞,事件佇列中的事件得不到及時處理。正因為這樣,nodejs中的一些庫方法均是非同步的,也提倡使用者呼叫非同步方法。

其實看到這裡的時候,如果有對windows程式設計(尤其對windows介面程式設計)比較了解的讀者可能已經聯想到了windows訊息迴圈。

沒錯,nodejs中的事件輪詢原理跟windows訊息迴圈的原理類似。開發者編寫的**均執行在主線程中,如果你編寫了阻塞**,在windows桌面程式中,由於訊息得不到及時處理,介面就會卡死。

咱們再來看一下下面的nodejs**:

var fs = require('fs');
fs.readfile('hello.txt', function (err, data) );
while(1)
如上,雖然我們使用非同步方法讀取檔案,但是檔案讀取完畢後「read file end」永遠不會輸出,也就是說readfile方法的**函式不會執行。原因很簡單,因為後面的while迴圈一直沒退出,導致下一次事件輪詢不能開始,所以**函式不能執行(包括其他所有**)。事實再次證明,開發者編寫的所有**均只能執行在同一執行緒之中(姑且稱之為主線程吧)。

關於非同步方法

所謂非同步方法,就是呼叫該方法不會阻塞呼叫執行緒,哪怕方法內部要進行耗時操作。你可以理解為方法內部單獨開闢了乙個新執行緒去處理任務,而呼叫非同步方法僅僅是開啟這個新執行緒。下面的**模擬乙個非同步方法的內部結構(僅僅是模擬,不代表實際):

public void dosomething(int arg1,asynccallback callback)
}).begininvoke(null,null);

}

如上**所示,呼叫dosomething方法不會阻塞呼叫執行緒。那麼對於每乙個非同步方法,怎樣去判斷非同步操作是否執行完畢呢?這時候必須給非同步方法傳遞乙個**函式作為引數,在.net中,這個**引數一般是asynccallback型別的。如大家所熟知的filestream.beginread/beginwrite以及socket.beginreceive/beginsend等等均屬於該類方法。

但是,我之所以要提非同步方法,就是想讓大家區分nodejs中的非同步方法和.net中非同步方法的乙個重大區別,雖然兩者內部原理可以理解為一致的,但是在**函式的呼叫方式這一點上,兩者有截然不同的方式。

在.net中,每個非同步方法的**函式均在另外乙個執行緒中執行(非呼叫執行緒),而在nodejs中,每個非同步方法的**函式仍然還在呼叫執行緒上執行。至於為什麼,大家可以看一下前面講事件輪詢的部分,nodejs中每個**函式均由主線程中的事件輪詢來呼叫。這樣才能保證在nodejs中,開發者編寫的任何**均在同乙個執行緒中執行(所謂的單執行緒)。

注:不懂呼叫執行緒、當前執行緒是什麼意思的同學可以看一下這篇部落格:《高屋建瓴:梳理程式設計約定》。

js事件迴圈 事件輪詢 event loop

總結了網上的一些有關event loop的資料 雖然負責解釋和執行js 的執行緒只有1個,但是瀏覽器內部還有其他執行緒來專門負責非同步任務的,比如定時器,ui,事件,網路等專門執行緒來負責相關任何的處理。step1 主線程讀取js 此時為同步環境,形成相應的堆和執行棧 step2 主線程遇到非同步任...

nodejs 事件驅動

其實這是兩部分內容 非同步i o 事件驅動 非同步i o就是nodejs是乙個非同步非阻塞語言 例如fs模組就能很好的理解。這裡不多贅述 今天主要談談 nodejs事件驅動 首先 明確一下什麼是 事件驅動 其實用js理解 就是一種事件監聽的方式 只不過 js是對於dom事件的監聽 而nodejs是對...

nodejs基於事件通訊

話說nodejs是非同步呼叫的,所以無法用return返回結果。有兩種解決方式 callback函式和事件。兩種方式相比起來事件的方式更加簡潔。nodejs裡的事件主要使用它的events模組,繼承eventemitter。這裡寫了乙個monitorevent類 var util require u...