Node.js 如何利用多核 CPU
Node.js 单线程默认只用一个核,利用多核的方案:cluster 模块 fork 多个 worker 共享端口、worker_threads 处理 CPU 密集计算、pm2 的 cluster 模式一键多进程,或容器编排层做水平扩容。
单个 JavaScript 主线程一次只在一个 CPU 核上执行,但 Node.js 进程也会使用其他内部线程。 需要并行执行 JavaScript 任务时, 吃满多核有三条路:① cluster 模块按核数 fork 多个进程共享同一端口;② worker_threads 在进程内开真正并行的线程做 CPU 密集计算;③ 用 pm2 等进程管理器的集群模式(本质是封装 cluster)。
cluster:多进程共享端口
const cluster = require('node:cluster');
const os = require('node:os');
if (cluster.isPrimary) {
const numCPUs = Math.min(4, os.availableParallelism()); // 演示上限,生产按资源预算设置
for (let i = 0; i < numCPUs; i++) cluster.fork();
cluster.on('exit', (worker) => {
console.log(`worker ${worker.process.pid} 退出`);
// 生产需区分计划退出和崩溃,并配置退避、重试上限与优雅关闭
});
} else {
const http = require('node:http');
http.createServer((req, res) => {
res.end(`来自进程 ${process.pid} 的响应`);
}).listen(3000);
}
除 Windows 外,cluster 默认通常由 primary 接收连接并轮询分发;也可使用共享监听句柄方式交由操作系统调度,并非 Linux 默认始终由内核分发。
worker_threads:进程内真并行
适合图像处理、加密、大数据计算这类 CPU 密集任务:
const { Worker } = require('node:worker_threads');
function runTask(input) {
return new Promise((resolve, reject) => {
const worker = new Worker('./heavy-task.js', { workerData: input });
worker.once('message', resolve);
worker.once('error', reject);
worker.once('exit', code => {
if (code !== 0) reject(new Error(`Worker exited: ${code}`));
});
});
}
runTask(10).then(console.log).catch(console.error);
对应 heavy-task.js 示例:
const { parentPort, workerData } = require('node:worker_threads');
parentPort.postMessage(workerData * workerData);
与 cluster 的区别:worker_threads 的普通 JS 对象和堆各自隔离,可显式共享 SharedArrayBuffer,适合计算;cluster 是独立进程,适合分摊请求。
pm2 一行搞定
pm2 start app.js -i max # 按核数起实例,自动负载均衡
pm2 reload app # 尝试滚动重载,需应用优雅退出/连接处理,不能保证零中断
常见问题(FAQ)
Q:fork 多少个 worker 合适?
先以 os.availableParallelism() 为参考,再按 CPU 配额、内存与负载测试限制数量;并非固定等于物理核数。此 API 需 Node 18.14+/19.4+。
Q:cluster 里共享状态怎么办?
各 worker 内存独立,session、计数器等共享状态要放 Redis 等外部存储,不能放进程内存。
Q:什么任务该用 worker_threads 而不是 cluster?
CPU 密集的 JavaScript 可用 worker_threads;已有异步压缩、crypto 等原生 API 可能已使用 libuv 线程池,不应一概再包 Worker;分摊网络请求用 cluster。两类问题,两个工具。