Node.js 事件循環技術指南

做者:Piero Borrelliphp

翻譯:瘋狂的技術宅前端

原文:blog.logrocket.com/a-complete-…node

未經容許嚴禁轉載web

每當我聽到人們談論Node.js時,就會出現不少關於到底是什麼,這項技術有什麼用處,以及其將來的問題。npm

讓咱們試着解決第一部分。回答這個問題最簡單的方法是列出許多 Node 技術上的定義:前端工程化

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

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

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

準確的理解 Node 在幕後的工做原理,不只會對這項技術瞭解的更多,還可以激發那些剛剛開始學習但還沒深刻使用的人們的興趣。函數

對於已是該領域的專業人士來講,瞭解它的內部和外部將使你成爲一個全新、前沿的開發人員,能夠根據你的需求去提升其性能。工具

所以,爲了挖掘 Node 的世界,咱們將檢視其核心部分:事件循環,實際上它是負責其非阻塞 I/O 模型的部分。

線程概述

在深刻了解事件循環以前,我想先在線程上花一些時間。若是你想知道這樣作的必要性,我會告訴你是爲了更好地理解一個概念,咱們必須先在本身的腦海中造成一個術語表,這將有助於咱們識別系統的每個部分。咱們會在稍後閱讀有關事件循環如何工做,以及如何將線程的概念應用於它的內容時,這最終將具備很大的優點。

每當咱們運行一個程序時,就會爲它建立一個實例,而且有一些內部調用線程與該實例相關。線程能夠看做是咱們的 CPU 必須執行的操做單元。許多不一樣的線程能夠與程序的單個進程相關聯。下面這個圖能夠幫你在腦海中造成這個想法:

瞭解線程

線程的簡圖

在討論線程時最重要的一點是:咱們的機器如何肯定在何時處理哪一個線程?

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

用於解決分配問題的機制稱爲 scheduling,它由操做系統中的調度程序管理。這背後的邏輯可能很是複雜,但總而言之,咱們能夠將執行此操做的兩種主要方式組合在一塊兒:

  • **多核機器:**爲不一樣的核心分配不一樣的線程。

多核機器處理線程的方式

多核機器如何處理線程

  • 使用可減小空置時間的優化邏輯: 這是最實用的方法。若是仔細研究一下線程是如何工做的,咱們將看到 OS 調度程序能夠識別 CPU 什麼時等待其餘資源執行一個做業,由此能夠分配它來同時執行其餘操做。這一般發生在代價很是昂貴的 I/O 操做上,例如從硬盤讀取數據。

事件循環

如今咱們已經對線程如何工做有了基本的瞭解,接下來解決 Node.js 事件循環邏輯。經過本文,你將瞭解前面那些解釋背後的緣由,每一條都會對應到正確的位置上。

每當運行 Node 程序時,都會自動建立一個線程。這個線程是整個代碼惟一執行的地方。在其中生成了一個被稱爲事件循環的東西。這個循環的做用是安排咱們惟一的線程應該在什麼時間點執行哪些操做。

詳細的說明

如今讓咱們嘗試模擬事件循環的工做原理及其工做方式。首先假設咱們正在用名爲 myProgram 的文件爲 Node 提供信息,而後詳細瞭解事件循環將對其進行的操做。

Feeding Node.js 示例文件

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

Node.js 事件循環的說明

步驟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 的重要部分,不管你是想得到有關此技術的更多看法,瞭解如何提升其性能,仍是找到學習新工具理由。

歡迎關注前端公衆號;前端先鋒,免費領取前端工程化實用工具包。

相關文章
相關標籤/搜索