
Node.js 工作線程雖然存在問題,但對我們來說效果極佳
這篇文章探討了 Node.js 單線程特性的限制,並解釋了 Inngest 如何利用工作線程來防止事件循環飢餓,確保在執行 CPU 密集型任務時仍能維持穩定的 WebSocket 心跳訊號。
背景
這篇文章探討了 Node.js 在處理高負載 CPU 任務時,單執行緒模型所面臨的事件循環阻塞問題。Inngest 團隊分享了他們如何利用 Worker Threads 來隔離使用者程式碼,以確保 WebSocket 心跳偵測等關鍵任務不會因為運算密集型工作而中斷,並詳細分析了 Node.js 多執行緒模型與 Go 或 Rust 等語言在設計哲學上的本質差異。
社群觀點
Hacker News 的討論聚焦於 Node.js Worker Threads 的定位與實務挑戰。許多開發者指出,將其稱為執行緒(Threads)容易產生誤導,因為它們在行為上更接近擁有獨立記憶體空間的子程序(Subprocesses)。這種設計雖然提供了極佳的隔離性與記憶體安全性,但也帶來了顯著的開發摩擦。最受詬病的點在於資料序列化的成本,當傳輸大型 JSON 或複雜物件時,結構化複製演算法(Structured Clone Algorithm)產生的效能損耗往往會抵銷多執行緒帶來的收益。有留言者分享,在處理 CPU 密集型工作時,若序列化成本過高,轉向使用 napi-rs 撰寫 Rust 擴充功能,或是直接分拆成獨立的子程序(Child Process)通常是更務實的選擇。
關於 Node.js 與其他語言的比較,社群內有不同的聲音。支持者認為 Node.js 強制開發者思考非同步與隔離模型,能有效避免傳統多執行緒中常見的鎖定與死鎖問題,且在處理數萬個併發連線時表現優異。然而,也有開發者批評 Node.js 的工具鏈對 Worker 並不友善,特別是 Bundler 和轉譯器在處理 Worker 檔案路徑時常顯得笨拙,這使得在函式庫中整合 Worker 功能變得異常困難。部分開發者期待未來能引入內聯模組(Inline Modules)提案,讓開發者能直接以 new Worker(module { ... }) 的方式啟動任務,從而改善目前的開發體驗。
此外,針對效能優化的討論也相當深入。有經驗的開發者建議,若要追求極致效能,應跳過訊息傳遞,改用 SharedArrayBuffer 配合 Atomics API 來共享原始記憶體,雖然這需要手動管理記憶體佈局,但能實現近乎零成本的資料共享。也有觀點提到,在雲端運算的時代,與其在單一容器內糾結於執行緒調度,不如直接利用基礎設施的水平擴展能力,將 CPU 密集型任務拆分到不同的微服務或容器中,這在維護性與資源監控上往往比管理複雜的 Worker 執行緒池更具優勢。
延伸閱讀
- Simple Node Multiprocess: 一個簡化 Node.js 多程序處理的實驗性專案。
- Platformatic Watt: 利用 Worker Threads 自動擴展至所有 CPU 核心的伺服器架構。
- JS Module Declarations Proposal: 旨在解決無法直接在程式碼中定義 Worker 邏輯的 TC39 提案。
- napi-rs: 用於撰寫高效能 Node.js 原生擴充功能的 Rust 框架。