Node.js事件驅動

來源:互聯網
上載者:User

Node.js事件驅動

   Node.Js是基於javascript語言,建構在google V8 engine以及Linux上的一個非阻塞事件驅動IO架構。這裡主要不是介紹nodejs具體應用代碼,而是想介紹一下事件驅動編程。

  Node.js事件驅動實現概覽

  雖然在ECMAScript的標準裡並沒有(也沒有必要)明確規定“事件”,但是在瀏覽器中,事件作為一個極為重要的機制,給予JavaScript響應使用者操作與DOM變化的能力;在Node.js中,非同步事件驅動模型則是其高並發能力的基礎。

  學習JavaScript也需要瞭解它的運行平台,為了更好的理解JavaScript的事件模型,我打算從Node及瀏覽器引擎源碼入手,分析其底層實現,並將我的分析整理為一系列博文;一方面作為筆記,另一方面也希望能與大家交流,分析和理解有疏漏偏頗之處,還望各位斧正。

  簡述事件驅動模型

  解釋JavaScript事件模型本身的好文章已經很多了,可以說這已經是一個說爛了的話題,這裡我只簡單寫一下,並且提供一些好文章的連結。

  程式如何響應事件

  我們的程式響應外部的事件有如下兩種方式:

  中斷

  作業系統處理鍵盤等硬體輸入就是通過中斷來進行的,這個方式的好處是即使沒有多線程,我們也可以放心地執行我們的代碼,CPU收到中斷訊號之後自動地轉去執行相應的中斷處理常式,處理完成後會恢複原來的代碼的執行環境繼續執行。這種方式需要硬體的支援,一般來說都會被作業系統封裝起來。

  輪詢

  迴圈檢測是否有事件發生,如果有就去執行相應的處理常式。這在底層和上層的開發中都有應用。

  Windows視窗程序就需要在主線程中寫下如下代碼,通常稱做訊息迴圈:

  ?

1

2

3

4

5

6

MSG msg = { };

while (GetMessage(&msg, NULL, 0, 0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

  訊息迴圈不斷檢測是否有訊息(使用者的UI操作、系統訊息等)出現,有的話就分發訊息,調用相應的回呼函數進行處理。

  輪詢方式的一個缺點就是:如果在主線程的訊息迴圈裡進行耗時操作,程式就無法及時響應新的訊息。這在JavaScript中表現明顯,以後還會提到這一點,並探討其解決方案。

  然而JavaScript中並沒有類似訊息迴圈代碼,我們只是簡單地註冊事件,然後等待被調用。這是因為瀏覽器、Node作為執行平台,已經將event loop實現了,JavaScript代碼不需要介入到這個過程中,只需要作為被調用者安靜地等待即可。

  Node中的event loop

  通過Node源碼看event loop的實現

  Node採用V8作為JavaScript的執行引擎,同時使用libuv實現事件驅動式非同步I/O。其事件迴圈就是採用了libuv的預設事件迴圈。

  在src/node.cc中,

  ?

1

2

3

4

5

6

7

8

Environment* env = CreateEnvironment(

node_isolate,

uv_default_loop(),

context,

argc,

argv,

exec_argc,

exec_argv);

  這段代碼建立了一個node執行環境,可以看到第三行的uv_default_loop(),這是libuv庫中的一個函數,它會初始化uv庫本身以及其中的default_loop_struct,並返回一個指向它的指標default_loop_ptr。

  之後,Node會載入執行環境並完成一些設定作業,然後啟動event loop:

  ?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

bool more;

do {

more = uv_run(env->event_loop(), UV_RUN_ONCE);

if (more == false) {

EmitBeforeExit(env);

// Emit `beforeExit` if the loop became alive either after emitting

// event, or after running some callbacks.

more = uv_loop_alive(env->event_loop());

if (uv_run(env->event_loop(), UV_RUN_NOWAIT) != 0)

more = true;

}

} while (more == true);

code = EmitExit(env);

RunAtExit(env);

...

  more用來標識是否進行下一輪迴圈。

  env->event_loop()會返回之前儲存在env中的default_loop_ptr,uv_run函數將以指定的UV_RUN_ONCE模式啟動libuv的event loop。在這種模式下,uv_run會至少處理一個事件:這意味著,如果當前事件隊列中沒有需要處理的I/O事件,uv_run會阻塞住,直到有I/O事件需要處理,或者下一個定時器時間到。如果當前沒有I/O事件也沒有定時器事件,則uv_run返回false。

  接下來Node會根據more的情況決定下一步操作:

  如果more為true,則繼續運行下一輪loop。

  如果more為false,說明已經沒有等待處理的事件了,EmitBeforeExit(env);觸發進程的'beforeExit'事件,檢查並處理相應的處理函數,完成後直接跳出迴圈。

  最後觸發'exit'事件,執行相應的回呼函數,Node運行結束,後面會進行一些資源釋放操作。

  在libuv中,定時器事件是直接在event loop中處理的,而I/O事件則分為兩類:

  Network I/O是使用系統提供的非阻塞式I/O解決方案,例如在Linux上使用epoll,windows上使用IOCP。

  檔案操作和DNS操作沒有(很好的)系統解決方案,因此libuv自建了線程池,在其中進行阻塞式I/O。

  另外我們也可以將自訂的函數拋到線程池中運行,在運行結束後主線程會執行相應的回呼函數,不過Node並沒有將這一項功能加入到JavaScript中,也就是說只用原生Node是無法在JavaScript中開啟新的線程進行並存執行的。

  以上所述就是本文的全部內容了,希望大家能夠喜歡。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.