Node.js Worker Threads:處理高負載 CPU 運算的解決方案

長期以來,「單執行緒」一直是 Node.js 的核心標籤。雖然這在處理 I/O 密集型任務(如網頁伺服器)時極具優勢,但當遇到極其耗費 CPU 的運算(如圖片轉檔、複雜加密、大型數值模擬)時,主執行緒會被卡死,導致所有請求停擺。Worker Threads 模組正是為了打破這個宿命而生。

為什麼需要 Worker Threads?

在 Node.js 中,如果你的程式碼包含一個耗時 5 秒的同步計算,這 5 秒內 Node.js 無法處理任何非同步事件(如 HTTP 請求)。Worker Threads 允許你開啟額外的執行緒,利用多核心 CPU 的優勢,在背景進行運算而不干擾主執行緒的運行。

基礎實戰範例

我們通常會在程式中判斷目前是「主執行緒」還是「工作者執行緒」:

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

if (isMainThread) {
  // --- 1. 主執行緒邏輯 ---
  console.log('主執行緒:開始委派重運算任務...');

  const worker = new Worker(__filename, {
    workerData: { num: 42 }, // 傳遞參數給工作者
  });

  worker.on('message', (result) => {
    console.log('成品回來了!計算結果:', result);
  });

  worker.on('error', (err) => console.error('工作出錯了:', err));
} else {
  // --- 2. 工作者執行緒邏輯 ---
  const heavyResult = fibonacci(workerData.num); // 執行耗時計算
  parentPort.postMessage(heavyResult); // 將成果送回主執行緒
}

function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

Worker Threads vs Child Process

雖然兩者都能避開主執行緒阻塞,但底層設計完全不同:

特性Child Process (子進程)Worker Threads (工作執行緒)
資源消耗 (獨立的 OS 程序) (同進程內的輕量執行緒)
通訊成本高 (資料需序列化/跨進程)低 (可共享記憶體)
記憶體隔離 (空間不互通)共享 (支援 SharedArrayBuffer)
適用場景執行外部 shell 指令純 JavaScript 重度計算

效能殺手鐧:共享記憶體 (Shared Memory)

Worker Threads 最強大的地方在於支援 SharedArrayBuffer。這讓多個執行緒可以同時讀寫同一塊記憶體區域,而不需要透過「訊息傳遞 (Message Passing)」進行大量的資料複製,這在處理海量數據運算時效能極佳。

小提醒:操作共享記憶體時需要使用 Atomics 模組,以避免發生競態條件 (Race Condition) 導致資料損毀。

總結

  1. 不要濫用:開啟執行緒是有開銷的。若任务是非同步 I/O(如讀檔案、查資料庫),絕對不要用 Worker Threads,用原生非同步才是王道。
  2. 適用場景:涉及大量迴圈、矩陣運算、加密解密、解析極大型 JSON 或圖片處理。
  3. 它讓 Node.js 終於具備了與傳統多執行緒語言(如 Java, C#)一較高下的運算能力。