Node.js 事件迴圈的完整指南

瘋狂的技術宅發表於2019-07-26
作者:Piero Borrelli

翻譯:瘋狂的技術宅

原文:https://blog.logrocket.com/a-...

未經允許嚴禁轉載

每當我聽到人們談論Node.js時,就會出現很多關於究竟是什麼,這項技術有什麼用處,以及其未來的問題。

讓我們試著解決第一部分。回答這個問題最簡單的方法是列出許多 Node 技術上的定義:

  • Node.js 是一個基於 Chrome 的 V8 JavaScript 引擎構建的 Javascript 執行時環境。
  • Node.js 使用事件驅動的非阻塞 I/O 模型,使其輕量且高效。
  • Node 包生態系統(npm)是全世界最大的開源庫生態系統。

但是,這些答案並不能令我滿意,因為有些東西不見了。在讀了上面的要點後,你可能會認為 Node.js 只是另一種 JavaScript 技術,但是如果你想要真正的理解它,最重要的是分析它是如何進行非同步操作的和它的非阻塞 I/O 系統。

這是每個 Web 開發人員應該必備的知識。

準確的理解 Node 在幕後的工作原理,不僅會對這項技術瞭解的更多,還能夠激發那些剛剛開始學習但還沒深入使用的人們的興趣。

對於已經是該領域的專業人士來說,瞭解它的內部和外部將使你成為一個全新、前沿的開發人員,可以根據你的需求去提高其效能。

因此,為了挖掘 Node 的世界,我們將檢視其核心部分:事件迴圈,實際上它是負責其非阻塞 I/O 模型的部分。

A brief refresh on threads

簡要重新整理執行緒

在深入瞭解事件迴圈之前,我想先在執行緒上花一些時間。如果你想知道這樣做的必要性,我會告訴你是為了更好地理解一個概念,我們必須先在自己的腦海中形成一個術語表,這將有助於我們識別系統的每一個部分。我們會在稍後閱讀有關事件迴圈如何工作,以及如何將執行緒的概念應用於它的內容時,這最終將具有很大的優勢。

每當我們執行一個程式時,就會為它建立一個例項,並且有一些內部呼叫執行緒與該例項相關。執行緒可以看作是我們的 CPU 必須執行的操作單元。許多不同的執行緒可以與程式的單個程式相關聯。下面這個圖可以幫你在腦海中形成這個想法:

clipboard.png

執行緒的簡圖

在討論執行緒時最重要的一點是:我們的機器如何確定在什麼時候處理哪個執行緒?

眾所周知,我們機器的資源是有限的(CPU,RAM),因此正確的決定怎樣分配它們是非常重要,換一種說法是,哪些操作優先於其他操作。這必須要做到,同時還要確操作不能消耗太多的時間 —— 沒有人喜歡執行速度慢的電腦。

用於解決分配問題的機制稱為 scheduling,它由作業系統中的排程程式管理。這背後的邏輯可能非常複雜,但總而言之,我們可以將執行此操作的兩種主要方式組合在一起:

  • 多核機器:為不同的核心分配不同的執行緒。

clipboard.png

多核機器如何處理執行緒

  • 使用可減少空置時間的優化邏輯: 這是最實用的方法。如果仔細研究一下執行緒是如何工作的,我們將看到 OS 排程程式可以識別 CPU 什麼時等待其他資源執行一個作業,由此可以分配它來同時執行其他操作。這通常發生在代價非常昂貴的 I/O 操作上,例如從硬碟讀取資料。

事件迴圈

現在我們已經對執行緒如何工作有了基本的瞭解,接下來解決 Node.js 事件迴圈邏輯。通過本文,你將瞭解前面那些解釋背後的原因,每一條都會對應到正確的位置上。

每當執行 Node 程式時,都會自動建立一個執行緒。這個執行緒是整個程式碼唯一執行的地方。在其中生成了一個被稱為事件迴圈的東西。這個迴圈的作用是安排我們唯一的執行緒應該在什麼時間點執行哪些操作。

詳細的說明

現在讓我們嘗試模擬事件迴圈的工作原理及其工作方式。首先假設我們正在用名為 myProgram 的檔案為 Node 提供資訊,然後詳細瞭解事件迴圈將對其進行的操作。

clipboard.png

特別是,我將首用一個簡短的圖來解釋,說明在事件迴圈 tick 過程中發生的事情,然後再以更深入的方式探討這些階段。

clipboard.png

步驟1:performChecks

不應該單純的認為事件迴圈實際上是一個迴圈。它有一個特定的條件,用來確定迴圈是否需要再次迭代。事件迴圈的每次迭代都被稱為一個 tick

事件迴圈執行 tick 的條件是什麼?

每當執行程式時,我們都會進行一系列需要執行的操作。這些操作主要分為三種型別:

  • 等待定時器操作(setTimeout()setInterval()setImmediate()
  • 等待處理中的作業系統任務
  • 等待需要長時間執行的操作

我稍後會詳細介紹這些內容;現在讓我們記住,只要其中一個操作處於掛起狀態,事件迴圈就會執行一個新的 tick。

步驟2:執行一個 tick

對於每個迴圈迭代,可以分為以下階段:

  • 階段1: Node 檢視其內部的掛起計時器集合,並檢查傳遞給 setTimeout()setInterval() 的回撥函式是否準備好在計時器過期的情況下被呼叫。
  • 階段2: Node 檢視其待處理 OS 任務的內部集合,並檢查哪些回撥函式已準備好被呼叫。一個例子是從機器的硬碟驅動器中完成了對檔案的檢索。
  • 階段3: Node 暫停其執行,等待新事件發生。新事件包括:新的計時器完成,新的OS任務完成,新的待處理操作完成。
  • 階段4: Node 檢查是否已經準備好呼叫與 setImmediate() 函式相關函式。
  • 第5階段: 管理關閉事件,用於清理程式狀態。

關於事件迴圈的常見問題和錯誤觀點

Node.js 是完全單執行緒的嗎?

這是對 Node.js 的一種非常普遍的誤解。 Node 執行在單個執行緒上,但是 Node.js 標準庫中包含的一些函式並不是(例如 fs 模組函式),他們的邏輯執行在 Node.js 執行緒之外。這樣做是為了保證程式的速度和效能。

這些其他執行緒執行在哪裡?

Node.js 會使用名為 libuv 的特殊庫模組來執行非同步操作。此庫還與 Node 的後臺邏輯一起使用,用來管理被稱為 libuv 執行緒池 的特殊執行緒池。

這個執行緒池由四個執行緒組成,用於委派對事件迴圈來說太重的操作。長時間執行的任務對於事件迴圈而言代價過於昂貴。

那麼事件迴圈是一種類似棧的結構?

從這個意義上說,雖然在上述過程中涉及一些類似棧的結構,但更精確的答案是事件迴圈由一系列的階段所組成,每個階段都有自己的特定任務,所有階段都以迴圈重複的方式去處理。如果想要知道關於事件迴圈確切結構的更多資訊,請檢視此演講

結論

瞭解事件迴圈是使用 Node.js 的重要部分,無論你是想獲得有關此技術的更多見解,瞭解如何提高其效能,還是找到學習新工具理由。


本文首發微信公眾號:前端先鋒

歡迎掃描二維碼關注公眾號,每天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公眾號,每天都給你推送新鮮的前端技術文章

歡迎繼續閱讀本專欄其它高贊文章:


相關文章