Node.js 多进程

cluster 榨干多核、child_process 调外部命令、worker_threads 做计算。

Advanced
Key PointsWeb 服务用 `cluster` 复用端口,外部命令用 `child_process`,纯计算用 `worker_threads`。

三种并发手段

ToolIsolationBest for
cluster独立进程、独立内存HTTP 服务多核
child_process独立进程调用 ffmpeg、git、python
worker_threads独立线程、共享内存可选图像、加密、大 JSON

cluster

JavaScript
const cluster = require('node:cluster');
const http = require('node:http');
const os = require('node:os');

if (cluster.isPrimary) {
  const n = os.cpus().length;
  console.log(`主进程 ${process.pid} 启动 ${n} 个 worker`);
  for (let i = 0; i < n; i++) cluster.fork();

  cluster.on('exit', (worker, code) => {
    console.log(`worker ${worker.process.pid} 退出(${code}),重启`);
    cluster.fork();
  });
} else {
  http.createServer((req, res) => {
    res.end(`由 ${process.pid} 处理\n`);
  }).listen(3000);
  console.log(`worker ${process.pid} 就绪`);
}
Output
主进程 24188 启动 8 个 worker
worker 24190 就绪
worker 24191 就绪
...

$ curl http://127.0.0.1:3000
由 24190 处理
$ curl http://127.0.0.1:3000
由 24191 处理

Multiple workers listening on one port are balanced by the primary (round-robin outside Windows). Re-fork in exit is what keeps the service alive.

child_process

JavaScript
const { exec, spawn } = require('node:child_process');
const { promisify } = require('node:util');

const execP = promisify(exec);

// 短命令:拿全部输出
const { stdout } = await execP('git rev-parse --short HEAD');
console.log('commit', stdout.trim());

// 长任务:流式读
const ff = spawn('ffmpeg', ['-i', 'in.mp4', 'out.mp3']);
ff.stderr.on('data', (d) => process.stderr.write(d));
ff.on('close', (code) => console.log('退出码', code));
exec hands the string to a shell, so interpolating user input is command injection. Pass arguments as an array with execFile or spawn.

worker_threads

JavaScript
// main.js
const { Worker } = require('node:worker_threads');

function runTask(n) {
  return new Promise((resolve, reject) => {
    const w = new Worker('./worker.js', { workerData: { n } });
    w.on('message', resolve);
    w.on('error', reject);
  });
}

const results = await Promise.all([runTask(40), runTask(41), runTask(42)]);
console.log(results);

// worker.js
const { parentPort, workerData } = require('node:worker_threads');

function fib(n) {
  return n < 2 ? n : fib(n - 1) + fib(n - 2);
}
parentPort.postMessage(fib(workerData.n));
Output
[ 102334155, 165580141, 267914296 ]

The same work on the main thread freezes the loop for seconds; in a worker the main thread keeps serving. This is the right pattern for CPU-bound work in Node.

该选哪个

  1. 要响应 HTTP 请求 → cluster(或直接用 PM2 的 cluster 模式)
  2. 要跑一个外部程序 → child_process
  3. 要算一个耗时算法 → worker_threads

Friends & Partners