Node.js worker_threads:并发 vs 并行
一、核心结论
Node.js 的 worker_threads
模块实现的是 并行计算 ,而非传统意义上的“并发”。其通过操作系统级线程实现多核 CPU 的并行执行,同时保留 Node.js 单线程事件循环的并发模型。
二、关键概念解析
1. 并发(Concurrency) vs 并行(Parallelism)
-
并发:
- 指系统同时处理多个任务的能力,但任务可能交替执行(如单核 CPU 通过时间片轮转)。
- Node.js 主线程 的事件循环是典型的并发模型,通过非阻塞 I/O 和事件队列实现。
-
并行:
- 指多个任务同时执行,需多核 CPU 支持,每个任务运行在独立核心上。
worker_threads
通过创建操作系统级线程实现并行计算。
2. Node.js 的线程模型
-
主线程:
- 单线程,运行事件循环,处理 I/O 和事件回调。
- 阻塞主线程会导致整个进程卡顿。
-
Worker 线程:
- 每个 Worker 线程是独立的 JavaScript 运行时,拥有自己的事件循环和堆内存。
- 线程由操作系统的系统线程支持,可绑定到不同 CPU 核心,实现并行执行。
三、worker_threads
的并行机制
1. 多核利用
-
默认线程数:
- Node.js 根据 CPU 核心数自动创建线程池(通常等于核心数)。
- 例如,4 核 CPU 默认创建 4 个线程,每个线程绑定到一个核心。
-
任务分配:
- 主线程通过
Worker
类创建子线程,并将任务通过postMessage
分发。 - 子线程执行任务后,通过
parentPort.postMessage
返回结果。
- 主线程通过
2. 线程调度
-
操作系统调度:
- Worker 线程由操作系统调度到不同 CPU 核心,实现真正的并行执行。
- 线程间通过
SharedArrayBuffer
或消息传递通信,避免阻塞主线程。
-
示例:并行计算斐波那契数列
// main.js const { Worker } = require('worker_threads'); const numCPUs = require('os').cpus().length;for (let i = 0; i < numCPUs; i++) {const worker = new Worker('./fibonacciWorker.js');worker.postMessage(40); // 每个线程计算第40个斐波那契数 }
// fibonacciWorker.js const { parentPort } = require('worker_threads');function fibonacci(n) {return n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2); }parentPort.on('message', (n) => {const result = fibonacci(n);parentPort.postMessage(result); });
四、对比传统并发模型
1. 事件循环(主线程)
-
特点:
- 单线程,通过非阻塞 I/O 和事件队列实现高并发。
- 适合 I/O 密集型任务(如网络请求、文件操作)。
-
局限:
- CPU 密集型任务会阻塞事件循环,导致其他任务无法执行。
2. Worker 线程(并行计算)
-
特点:
- 多线程,利用多核 CPU 并行执行 CPU 密集型任务。
- 线程间通过消息传递或共享内存通信,避免阻塞主线程。
-
适用场景:
- 大型计算(如加密、图像处理、机器学习)。
- 并行化数据库查询或数据处理任务。
五、性能监控与验证
1. 监控 CPU 核心利用率
-
Linux:
top -H -p <PID> # 查看进程内所有线程的CPU使用率
-
Node.js:
const { threadId } = require('worker_threads'); console.log(`Worker ${threadId} 正在运行`);
2. 并行计算验证
-
示例:计算 π 的近似值(蒙特卡洛方法)
// main.js const { Worker } = require('worker_threads'); const numWorkers = 4; const totalSamples = 1e8;const workers = Array.from({ length: numWorkers }, (_, i) => {const worker = new Worker('./piWorker.js');worker.postMessage({ samples: totalSamples / numWorkers });return worker; });let results = []; workers.forEach((worker, i) => {worker.on('message', (result) => {results[i] = result;if (results.length === numWorkers) {const pi = results.reduce((sum, val) => sum + val) / numWorkers;console.log(`π ≈ ${pi}`);}}); });
// piWorker.js const { parentPort } = require('worker_threads');parentPort.on('message', ({ samples }) => {let inside = 0;for (let i = 0; i < samples; i++) {const x = Math.random();const y = Math.random();if (x * x + y * y <= 1) inside++;}parentPort.postMessage(4 * inside / samples); });
六、总结
-
worker_threads
是并行计算:- 利用多核 CPU,通过操作系统级线程实现任务并行执行。
- 适用于 CPU 密集型任务,避免阻塞主线程的事件循环。
-
与主线程的并发模型互补:
- 主线程处理 I/O 和事件驱动的并发。
- Worker 线程处理 CPU 密集型任务的并行计算。
通过合理使用 worker_threads
,您可以充分发挥多核 CPU 的性能优势,构建高效、响应迅速的 Node.js 应用。