多執行緒指南:探究多執行緒在Node.js中的廣泛應用

2023-10-25 18:01:09

前言

最初,JavaScript是用於設計執行簡單的web任務的,比如表單驗證。直到2009年,Node.js的建立者Ryan Dahl讓開發人員認識到了通過JavaScript 進行後端開發已成為可能,在後端開發中,用到最多的就是多執行緒以及執行緒之間的同步功能,今天小編就為大家介紹一下如何使用Node.js實現多執行緒的應用。

Node.js的內部工作原理

在介紹之前,先給大家介紹一下Node.js的工作原理,Node.js基於單執行緒事件迴圈的範例進行操作。為了充分掌握Node.js的功能,理解Node中執行緒(構成Node.js核心的事件迴圈)的概念至關重要。

Node.js中的執行緒

在Node.js中,執行緒是指單個程序內的獨立執行上下文,它是一個輕量級的處理單元,可以與同一程序中的其他執行緒並行操作。每個執行緒都有自己的執行指標和堆疊,並共用程序堆。

Node.js使用兩種型別的執行緒:由事件迴圈管理的主執行緒和工作池中的多個輔助執行緒。(在本文中」輔助執行緒「和"執行緒"可互換使用來指代工作執行緒)

Node.js中的主執行緒是Node.js啟動時的初始執行執行緒,它負責執行JavaScript程式碼並處理傳入的請求,工作執行緒是與主執行緒並行執行的單獨執行執行緒。

Node.js 以多執行緒還是單執行緒方式執行?

「單執行緒」是指只有一個執行執行緒的程式,允許它順序執行任務,「多執行緒」意味著具有多個執行執行緒的程式可以同時執行任務。

通常情況下,Node.js 被認為是單執行緒,因為它只有一個處理 JavaScript 操作和 I/O 的主事件迴圈。然而,Node.js單執行緒架構中的主要元素是事件迴圈,這使得 Node.js 儘管是單執行緒執行,卻有著強大的效能。

事件迴圈

事件迴圈是一種註冊將要執行的回撥(函數)的機制,並與 JavaScript 程式碼在同一執行緒中執行。當 JavaScript 操作阻塞執行緒時,事件迴圈也會被阻塞。

工作池

工作池是一種執行模型,它生成並管理單獨的執行緒,這些執行緒同步執行任務並將結果返回到事件迴圈。然後,事件迴圈使用結果執行提供的回撥。工作池主要用於非同步 I/O 操作,例如與系統磁碟和網路的互動,並在libuv中實現。儘管當 Node.js 需要在 JavaScript 和 C++ 之間進行內部通訊時可能會出現輕微的延遲,但幾乎不會被注意到。

使用事件迴圈和工作池實現非同步操作

藉助事件迴圈和工作池機制,能夠在 Node.js 中編寫有效處理非同步操作的程式碼。

fs.readFile(path.join(__dirname, './package.json'), (err, content) => {
 if (err) {
   return null;
 }
 console.log(content.toString());
});

介紹worker_threads模組

worker_threads模組是一個包,它允許在單核上建立多個執行緒,下面是worker_threads的使用方法:

type WorkerCallback = (err: any, result?: any) => any;
export function runWorker(path: string, cb: WorkerCallback, workerData: object | null = null) {
const worker = new Worker(path, { workerData });
worker.on('message', cb.bind(null, null));
worker.on('error', cb);
worker.on('exit', (exitCode) => {
   if (exitCode === 0) {
     return null;
   }
   return cb(new Error(`Worker has stopped with code ${exitCode}`));
 });
 return worker;
}

先建立一個Worker類的範例,第一個引數包含worker程式碼的檔案路徑,第二個引數應該是一個包含名為workerData的屬性的物件,並在開始執行時能夠存取的資料。

需要注意的是,無論是使用 JavaScript 還是TypeScript,檔案路徑都應始終指向擴充套件名為 .js 或.mjs的檔案。

下面是一些常見的事件:

/*每當工作執行緒中發生未處理的異常時,會觸發錯誤事件。隨後,工作執行緒被終止,
並且可以將錯誤作為提供的回撥函數中的第一個引數進行存取。這種設定可以實現及時捕獲和處理異常情況。
*/
worker.on('error', (error) => {});
/*
當工作執行緒退出時,會發出exit事件。如果呼叫process.exit(),exitCode將提供給回撥函數。
如果使用worker.terminate()終止worker ,退出程式碼將被設定為1:
*/
worker.on('exit', (exitCode) => {});
/*
當工作執行緒向父執行緒傳送資料時,會發出訊息事件。現在,來看看資料是如何線上程之間共用的。
*/
worker.on('online', () => {});

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

工作程式

第一種方法是生成一個工作程式,執行其程式碼,並將結果傳送回父級。然而,這種方法具有顯著的開銷成本,包括建立新的工作執行緒、管理每個執行緒的記憶體開銷以及啟動和管理執行緒所需的資源。雖然可以使用這種方法完成任務,但它可能效率不高,尤其是在大規模基於節點的系統中。為了解決與此方法相關的挑戰,通常採用第二種更常用的行業實踐。

工作執行緒池

第二種方法是實現工作執行緒池,它通過建立可重用於多個任務的工作執行緒池來減輕第一種方法的缺點。不是為每個任務建立一個新的工作執行緒,而是建立一個工作執行緒池,並將任務分配給它們。

用技術術語來說,工作池可以被視為管理工作執行緒池的抽象資料型別。池中的每個工作執行緒都被分配一個任務,並且該執行緒與其他執行緒並行執行該任務。

在工作池中分配任務的方式有多種,池充當管理器,將任務分配給工作執行緒,收集它們的結果,並促進池中執行緒之間的通訊。

實現工作池可能涉及使用不同的資料結構和演演算法,例如任務佇列和訊息傳遞系統。具體資料結構的選擇取決於多種因素,包括所需的工作執行緒數量、任務的性質以及執行緒之間所需的通訊級別。

Node.js實現工作池

在 Node 中,可以使用內建功能或第三方工具來實現工作池。節點的內建工作執行緒模組提供對工作執行緒的支援,可用於建立工作池。此外,還有多個庫可以通過為工作執行緒提供高階 API 以及對任務排程和執行緒管理的額外支援來補充工作池。

這些庫自動執行任務排程和執行緒管理過程,從而更容易實現工作池。

為了說明這一點,下面是一個利用Node內建工作執行緒功能的範例程式碼:

const { Worker, isMainThread, parentPort } = require('worker_threads');

if (isMainThread) {
  // Main thread code
  // Create an array to store worker threads
  const workerThreads = [];
  // Create a number of worker threads and add them to the array
  for (let i = 0; i < 4; i++) {
    workerThreads.push(new Worker(__filename));
  }
  // Send a message to each worker thread with a task to perform
  workerThreads.forEach((worker, index) => {
    worker.postMessage({ task: index });
  });
} else {
  // Worker thread code
  // Listen for messages from the main thread
  parentPort.on('message', message => {
    console.log(`Worker ${process.pid}: Received task ${message.task}`);
    // Perform the task
    performTask(message.task);
  });
  function performTask(task) {
    // … operations to be performed to execute the task
  }
}

上面的程式碼由兩部分組成:一部分用於主執行緒,另一部分用於工作執行緒。在主執行緒部分,從模組中匯入必要的成員,如果當前執行上下文在主執行緒中,則建立一個陣列來儲存四個worker。隨後,帶有要執行的任務的新訊息被傳送到每個工作執行緒。

在工作執行緒部分,使用屬性方法來監聽來自主執行緒的訊息parentPort。一旦收到訊息,記錄下程序ID和任務,並將任務傳遞給應用程式中適當的方法來執行。這樣就能夠更加智慧地處理任務,並提供高效的函數執行。

使用執行緒的主要優點是什麼?

執行緒是一個強大的工具,可以極大地影響程式的效能、響應能力和整體效率。在 Node.js 中,執行緒對於開發人員來說是一項很有價值的功能,因為它可以將程序拆分為多個獨立的執行流。如果正確使用,執行緒可以提高程式的速度、效率和響應能力。

執行緒的優勢:

  1. 提高效能:執行緒允許並行執行多個任務,與順序執行任務相比,整體程式執行速度更快。
  2. 響應性:執行緒可以防止計算量大的任務阻塞或延遲其他操作的執行,確保程式保持對使用者輸入和其他任務的響應。
  3. 資源共用:Node.js 中的執行緒可以共用變數等資源,從而實現並行處理並加快程式執行速度。
  4. 易於程式設計:執行緒消除了 Node.js 中單執行緒架構的限制,使程式設計更加高效和可延伸。
  5. 提高可延伸性:執行緒可以輕鬆擴充套件,從而可以更輕鬆地構建高效能且可延伸的 Node.js 應用程式,這些應用程式可以輕鬆處理增加的負載。

結論

通過worker_threads模組,可以輕鬆地將多執行緒支援整合到應用程式中。將密集的CPU計算解除安裝到單獨的執行緒中,可以大幅提高伺服器的吞吐量。這種設計可以吸引更多來自人工智慧、機器學習和巨量資料等領域的開發人員和工程師開始在他們的專案中使用Node.js。因此,使用worker_threads模組是一種高效、便捷的方式來實現多執行緒程式設計。


Redis從入門到實踐

一節課帶你搞懂資料庫事務!

Chrome開發者工具使用教學

擴充套件連結:

從表單驅動到模型驅動,解讀低程式碼開發平臺的發展趨勢

低程式碼開發平臺是什麼?

基於分支的版本管理,幫助低程式碼從專案交付走向客製化化產品開發