深入瞭解Node.js 中的多執行緒和多程序

2020-09-30 21:00:20

Node.js 是一個免費的跨平臺 JavaScript 執行時環境,儘管它本質上是單執行緒的,但是可以在後臺使用多個執行緒來執行非同步程式碼。

由於 Node.js 的非阻塞性質,不同的執行緒執行不同的回撥,這些回撥首先委託給事件迴圈。 Node.js 執行時負責處理所有這一切。

為什麼要使用NodeJS?

JavaScript 最初是作為一種單執行緒程式語言構建的,僅在 Web 瀏覽器中執行。這意味著在一個過程中,只有一組指令能夠在給定的時間執行。

僅在當前程式碼塊的執行完成後,才移至下一個程式碼塊。但是,JavaScript 的單執行緒性質使實現變得容易。

最初,JavaScript 對於僅用於向網站新增少量互動。所以並沒有對多執行緒的需求。但是時代已經變了,使用者要求也越來越高,JavaScript 已成為「Web 上流行的程式語言」。

多執行緒現在變得很普遍。由於 JavaScript 是單執行緒語言,因此無法在其中實現多執行緒。幸運的是,在這種情況下,有一個很好的解決方法:Node.js。

Node.js 框架並不少,這要歸功於 JavaScript 執行時環境(尤其是 JavaScript)的普遍流行。在繼續本文之前,讓我們瞭解一些有關 Node.js 的重要觀點:

  1. 可以用 send 函數將訊息從子程序傳遞到其他子程序和主程序
  2. 支援 fork 多個程序
  3. 主程序和子程序之間不共用狀態

為什麼要 fork 程序?

在兩種情況下,我們需要 fork 一個流程:

  1. 通過將任務委派給其他程序來提高速度
  2. 用於釋放記憶體和解除安裝單個程序

可以將資料傳送到子程序,也可以將其送回。

Node.js 的方式

Node.js 使用兩種型別的執行緒:

  1. 通過事件迴圈處理主執行緒,
  2. 工作池中有許多輔助執行緒

事件迴圈負責獲取回撥或函數,並將其註冊以供將來執行。它與正確的 JavaScript 程式碼在同一執行緒中執行。一旦 JavaScript 操作阻塞了執行緒,事件迴圈也會被阻塞。

工作池是一個執行模型,負責產生和處理不同的執行緒。它同步執行任務,然後將結果返回到事件迴圈,最後事件迴圈將結果提供給回撥。

總而言之,工作池負責非同步 I/O 操作,即與系統磁碟和網路的互動。像 fs 和 crypto 這樣的模組是使用工作池的主要模組。

由於工作池是在 libuv 庫中實現的,Node.js 在 JS 和 C++ 之間進行內部通訊時會稍有延遲。不過這幾乎是不可察覺的。

一切都很好,直到我們遇到同步執行復雜操作的要求。任何需要大量時間執行的函數都會導致主執行緒阻塞。

如果程式具有多個佔用大量 CPU 的函數,將會導致伺服器吞吐量的顯著下降。在最壞的情況下,伺服器將會失去響應,並且無法將任務委派給工作池。

諸如 AI、巨量資料和機器學習之類的領域無法從 Node.js 中受益,因為這些操作阻塞了主執行緒,並使伺服器失去響應。但是這隨著 Node.js v10.5.0 的到來而改變,該版本增加了對多執行緒的支援。

並行和 CPU 繫結任務的挑戰

在 JavaScript 中建立並行可能很困難。允許多個執行緒存取相同的記憶體會導致競爭狀態,這不僅使故障難以重現,而且解決起來也很困難。

Node.js 最初被實現為基於非同步 I/O 的伺服器端平臺。通過簡單地消除執行緒需求,這使很多事情變得容易。是的,Node.js 程式是單執行緒的,但不是典型的方式。

我們可以在 Node.js 中並行執行,但是不需要建立執行緒。作業系統和虛擬機器器共同並行使用 I/O,然後在需要將資料傳送回 JavaScript 程式碼時,JS 程式碼在單個執行緒中執行。

除 JS 程式碼外,所有內容均在 Node.js 中並行執行。與非同步塊不同,JS 的同步塊總是一次執行一次。與程式碼執行相比,等待 JS 中產生 I/O 事件所話費的時間要多得多。

Node.js 程式僅呼叫所需的函數或回撥,而不會阻止其他程式碼的執行。最初 JavaScript 和 Node.js 都不打算處理 CPU 密集型或 CPU 繫結的任務。

當程式碼最少時,執行將會是敏捷的。但是計算量越大,執行速度就越慢。

如果你仍然嘗試在 JS 和 Node 中完成 CPU 密集型任務,那麼將會使瀏覽器中的 UI 凍結並對所有 I/O 事件進行排隊處理。儘管如此,我們已經走了很遠。現在有了 worker_threads 模組。

worker_threads 模組使多執行緒變得簡單

Node.js v10.5.0 於 2018 年 6 月釋出,引入了 worker_threads 模組。它有助於在流行的 JavaScript 執行時環境中實現並行。該模組允許建立功能齊全的多執行緒 Node.js 應用。

從技術上講,工作執行緒是在單獨的執行緒中產生的一些程式碼。要開始使用輔助執行緒,需要先匯入 worker_threads 模組。之後需要建立 Worker 類的範例以建立工作執行緒。

建立 Worker 類的範例時,有兩個引數:

  1. 第一個引數提供擴充套件名 .js 或 .mjs 的檔案路徑,其中包含工作程式執行緒的程式碼,
  2. 第二個引數提供了一個包含 workerData 屬性的物件,該屬性包含工作執行緒開始執行時將存取的資料

輔助執行緒能夠排程多個訊息事件。因此,回撥方法優先於返回 promise。

工作執行緒之間的通訊是基於事件的,即偵聽器設定為在工作執行緒傳送事件後立即呼叫。最常見的 4 個事件是:

worker.on('error', (error) => {});
  1. 當工作執行緒中有未捕獲的異常時發出。接下來工作執行緒終止,並且該錯誤可以作為回撥中的第一個引數使用。
worker.on('exit', (exitCode) => {})
  1. 當輔助執行緒退出時發出。如果在工作執行緒中呼叫了 process.exit(),則會將 exitCode 提供給回撥。如果 worker.terminate() 終止工作執行緒,則程式碼為 1。
worker.on('message', (data) => {});
  1. 當工作執行緒將資料傳送到父執行緒時發出。
worker.on('online', () => {});
  1. 當工作執行緒停止解析 JS 程式碼並開始執行時發出。儘管不常用,但 online 事件在特定情況下可能會提供更多資訊。

使用工作執行緒的方式

有兩種使用工作執行緒的方法:

  • 方法 1 – 涉及產生工作執行緒,執行其程式碼並將結果傳送到父執行緒。此方法需要每次為新任務從頭建立新的 worker 執行緒。
  • 方法 2 – 涉及生成 worker 執行緒併為訊息事件設定偵聽器。每次觸發該訊息時,輔助執行緒都會執行程式碼,並將結果傳送回父執行緒。輔助執行緒保持活動狀態,以備將來使用。

方法 2 也被稱為工作池。這是因為該方法涉及建立 worker 的工作池,先讓他們等待,並在需要時去排程訊息事件來執行任務。

由於從頭建立工作執行緒需要建立虛擬機器器以及解析和執行程式碼,因此官方 Node.js 檔案 建議採用方法 2。此外,方法 2 更為實用,比方法 1 更有效。

worker_threads 模組中可用的重要屬性

  • isMainThread – 當不在工作執行緒內操作時,此屬性為 true。如果需要,則可以在 worker 檔案的開頭包含一個簡單的 if 語句。這樣可以確保它僅作為工作執行緒執行。
  • parentPort – MessagePort 的範例,用於與父執行緒進行通訊。
  • threadId – 分配給工作執行緒的唯一識別符號。
  • workerData – 包含在 worker 執行緒的建構函式中的資料。

Node.js 中的多程序

為了使 Node.js 利用多核系統的功能,可以用一些程序。流行的 javascript 執行時環境中有稱被為 cluster 的模組,該模組提供對多程序的支援。

使用 cluster 模組可以產生多個子程序,這些子程序可以共用一個公共埠。當子程序投入使用時,使用 NodeJS 的系統可以處理更大的工作量。

後端的 Node.js

網際網路已經成為全球數以百萬計公司的首選平臺。因此,為使一家企業發揮最大潛力,並在此過程中脫穎而出,必須擁有強大的網路形象。

這一切都始於一個強大而直觀的網站。要打造一個完美無瑕的網站,重要的是選擇最佳的前端和後端技術。儘管本質上是單執行緒的,但 Node.js 是開發後端 Web 服務的首選。

儘管有大量的後端多執行緒選擇,但知名公司還是喜歡 Node.js。這是因為 Node.js 提供了在 JavaScript 中使用多執行緒的變通方法,而 JavaScript 已經是「Web上最流行的程式語言」。

總結

worker_threads 模組提供了一種在 Node.js 程式中實現多執行緒的簡便方法。通過將繁重的計算委派給工作執行緒,可以顯著提高伺服器的吞吐量。

藉助對多執行緒的支援,Node.js 將繼續吸引越來越多的來自 AI、巨量資料和機器學習等計算密集型領域的開發人員、工程師和其他專業人員。

更多程式設計相關知識,請存取:!!

以上就是深入瞭解Node.js 中的多執行緒和多程序的詳細內容,更多請關注TW511.COM其它相關文章!