深刻解析Node.js事件循環工做機制


深刻解析Node.js事件循環工做機制


image.png



做者 | Piero Borrelli
譯者 | 張蘭月編輯 | 張之棟,Yonie
本文從對線程、事件循環、事件循環常見的問題和錯誤上分別進行說明,進一步探索了 Node 的核心工做原理。

每當人們談論 Node.js 時,都會出現不少問題,好比它到底是什麼、這項技術有什麼用、它是否有將來等等。node

讓咱們嘗試討論第一部分。回答這個問題最簡單的方法是列出 Node 在技術上的許多定義,如: 
  • Node.js 是一個基於 Chrome V8 JavaScript 引擎構建的 Javascript 運行時環境。
  • Node.js 使用事件驅動的非阻塞 I/O 模型,這使其輕量且高效。
  • Node 包生態系統 (npm) 是全世界最大的開源庫生態系統。

可是,這些答案並不能令我徹底滿意,由於缺乏一些東西。在閱讀上面的要點後,你可能會認爲 Node.js 只是另外一種 JavaScript 技術,但理解它最重要的方法是分析它是如何實現異步並具備徹底非阻塞 I/O 系統的。npm

這纔是爲何它能成爲每一個 Web 開發人員必備之物的真正緣由。異步

準確瞭解 Node 如何在幕後工做不只能增進對這項技術的更多瞭解,並且還會吸引那些還未用過它的人認識並開始學習它。ide

而對於那些已是該領域專業人士的人來講,瞭解 Node 的內部和外部將使你成爲一名最新和最前沿的開發人員,而且可以根據自身的需求提升 Node 的性能。函數

所以,爲了深刻了解 Node ,咱們將解析其核心部分——事件循環的工做機制。事實上,事件循環就是負責 Node 非阻塞 I/O 模型的部分。工具

刷新對線程的認知

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

每當咱們運行一個程序時,咱們都會建立一個它的實例,而且咱們會調用一些內部線程,他們是與該實例相關聯的。線程能夠看做是 CPU 必須執行的操做單元。許多不一樣的線程能夠與程序的單個進程相關聯。如下是一個圖形,它能夠幫助你在腦海中造成這個概念:image.png性能

線程的簡單圖形學習

在談論線程時,最重要的一點是 :計算機在某個時刻應該處理哪一個線程?優化

衆所周知,咱們的機器資源(CPU、RAM)是有限的,所以正確分配資源很是重要,或者說,哪些操做應該優先執行。這一切都必須實現,並且同時,須要確保全部操做不能耗費太多時間,由於沒有人喜歡筆記本電腦速度過慢。

用於解決資源分配問題的機制就叫做調度,它由咱們的操做系統中稱爲 OS 調度程序的實體來管理。這背後的邏輯可能很是複雜,但總而言之,咱們能夠將執行此操做的兩大方法組合在一塊兒:
  • 多核機器 爲不一樣的核分配不一樣的線程。

  • image.png

多核機器如何處理線程

  • 使用優化邏輯,以減小死鎖:這是對咱們來講最切實的方法。若是咱們仔細研究一下線程是如何工做的,咱們將看到操做系統調度器能夠識別出CPU在什麼時候等待其餘資源來執行一個做業,所以它能夠被分配來同時執行其餘操做。這一般發生在很是繁重的 I/O 操做上,例如硬盤讀取。

事件循環

如今咱們已經對線程的工做原理有了新的瞭解,咱們終於能夠解決Node.js 事件循環邏輯了。經過閱讀本文,你會了解前面的解釋它背後的緣由,並且每一個部分都會自行找到正確的位置。

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

請注意:在咱們運行程序後那一剎那,事件循環不會馬上生成。實際上,只有在整個程序執行完畢後事件循環纔會運行。

詳情

如今讓咱們嘗試模擬事件循環的工做原理以及它如何使咱們的程序開始工做。爲此,我將僞裝本身正在使用一個名爲 myProgram 的文件爲 Node 提供信息,而後咱們再詳細瞭解事件循環將執行的全部操做。

特別的,我將首先編寫一個簡短的圖形解釋,來講明在某個事件循環 tick 過程當中發生了什麼,而後我將以更深刻的方式探討這些階段。image.png

事件循環的圖形說明

 第1步:performChecks

我不該該告訴你事件循環其實是一個循環。這意味着它有一個特定的條件,這個條件將決定循環是否須要再次迭代。事件循環的每次迭代都稱爲 tick

事件循環執行 tick 的條件是什麼?

每當咱們執行程序時,咱們都會有一系列須要執行的操做。這些操做可分爲三個大類:

  • 掛起的定時器操做 (setTimeout(), setInterval(),setImmediate())。
  • 掛起的操做系統 (OS) 任務。
  • 掛起的長時間運行操做的執行。

咱們稍後會詳細介紹這些內容;如今,讓咱們記住,只要其中一個操做處於掛起狀態,事件循環就會執行一個新的 tick。

第2步:執行 tick

對於每一個循環迭代,咱們能夠將其分爲如下階段:
  • 階段 1:Node 查看掛起的計時器的內部集合,並檢查傳遞給setTimeout()和setInterval()的回調函數是否準備好在計時器過時的狀況下被調用。
  • 階段 2:Node 查看掛起的 OS 任務的內部集合,並檢查哪些回調函數已準備好被調用。從計算機的硬盤驅動器中檢索文件便是一個例子。
  • 階段 3:Node 暫停執行,等待新事件的出現。新事件包括:新的計時器完成、新的 OS 任務完成和新的掛起操做完成。
  • 階段 4:Node 檢查是否準備好調用與掛起定時器(掛起定時器與setImmediate()函數相關)相關的任何函數。
  • 階段 5:管理關閉事件,用於清理應用程序的狀態。

常見問題和錯誤認知  Node.js 是徹底單線程的嗎?

這是對這項技術的一種很是廣泛的誤解。雖然 Node 是在單個線程上運行,可是 Node.js 標準庫中包含的有些函數並非如此(例如 fs 模塊函數);它們的邏輯運行在 Node.js 單線程以外,這樣作是爲了保持程序的速度和性能。

  這些其餘線程在哪裏外包?

使用 Node.js 時,會使用一個名爲 libuv 的特殊庫模塊來執行異步操做。此庫還與 Node 的後向邏輯一塊兒被用來管理稱爲 libuv 線程池 的特殊線程池。

此線程池由四個線程組成,它們負責委派對事件循環來講太繁重的操做。

  那麼事件循環是一種相似堆棧的結構嗎?

從這個意義上說,雖然在上述過程當中涉及到了一些相似堆棧的結構,但更準確的答案是事件循環由一系列階段組成,每一個階段都有本身的特定任務,並且全部階段都以循環重複的方式被處理。有關事件循環的更多信息,請看這個對話: https://www.youtube.com/watch?v=PNa9OMajw9w。

  結論  

瞭解事件循環是使用 Node.js 的重要部分,不管你是想得到有關此技術更多看法、瞭解如何提升其性能,仍是但願找到學習一個新工具的理由,本文都會對你有所幫助。

英文原文: https://blog.logrocket.com/a-complete-guide-to-the-node-js-event-loop/  

相關文章
相關標籤/搜索