Node.js 多进程(快速上手)

Node.js 多进程:让应用跑得更快更稳

在日常开发中,我们常常会遇到这样的场景:一个 Node.js 服务在处理大量请求时,响应变慢,甚至出现卡顿。这背后的根本原因,往往是因为 Node.js 的单线程特性。虽然 V8 引擎非常高效,但一旦遇到 CPU 密集型任务(比如图像处理、数据压缩、复杂计算),整个进程就会被“堵住”,其他请求只能排队等待。

这时候,我们就需要引入“Node.js 多进程”这一核心机制。简单来说,就是让一个应用同时运行多个独立的 Node.js 进程,每个进程都拥有自己的内存空间和执行线程,互不干扰。这样一来,即使某个进程在处理耗时任务,也不会影响其他进程的正常运行。

想象一下,你开了一家餐厅。如果只有一个厨师(单线程),那他只能一个一个做菜。一旦遇到需要长时间炖汤的任务,其他顾客的点单就得等。但如果雇了多个厨师(多进程),每个人负责不同的菜系,哪怕有人在慢炖,其他人也能继续炒菜、蒸饭,整体效率大大提升。

Node.js 的 child_process 模块正是实现这一机制的关键工具。它允许主进程创建子进程,通过 IPC(进程间通信)进行数据交换。接下来,我们一步步深入理解它的用法。

什么是进程?为什么需要多进程?

在操作系统层面,进程是程序的一次运行实例。每个进程都有独立的内存空间、系统资源和执行上下文。Node.js 默认是单进程运行的,这意味着所有代码都在同一个线程中执行。

但当遇到 CPU 密集型任务时,比如用 JavaScript 做复杂的数学运算或图像处理,整个程序就会被阻塞。因为 Node.js 的事件循环无法并行处理这些任务。

而“Node.js 多进程”就是通过创建多个子进程,把 CPU 密集型任务分配到不同进程中去执行,主进程则专注于处理 I/O 操作(如 HTTP 请求、数据库查询),实现“分工协作”。

举个例子:你有一个任务是将 1000 张图片批量压缩。如果用单进程处理,需要等所有图片处理完才能返回结果。但用多进程,你可以把任务拆成 4 份,交给 4 个子进程并行处理,时间直接缩短到原来的四分之一。

使用 child_process 模块创建子进程

Node.js 内置的 child_process 模块提供了多种方式创建子进程,最常用的是 fork()spawn()

fork():专门用于 Node.js 子进程

fork()spawn() 的一个特例,专为运行 Node.js 脚本设计。它会自动建立双向 IPC 通道,方便主进程与子进程通信。

// main.js - 主进程
const { fork } = require('child_process');

// 启动一个子进程,执行 worker.js 文件
const worker = fork('./worker.js');

// 监听子进程发送的消息
worker.on('message', (data) => {
  console.log('主进程收到子进程消息:', data);
});

// 向子进程发送消息
worker.send({ action: 'start', payload: 1000 });

// 监听子进程退出
worker.on('exit', (code) => {
  console.log('子进程退出,退出码:', code);
});
// worker.js - 子进程代码
process.on('message', (msg) => {
  console.log('子进程收到主进程消息:', msg);

  // 模拟耗时计算
  let result = 0;
  for (let i = 0; i < msg.payload; i++) {
    result += Math.sqrt(i);
  }

  // 将结果发回主进程
  process.send({ result, from: 'worker' });
});

注释说明

  • fork('./worker.js'):启动一个独立的 Node.js 进程运行 worker.js。
  • process.send():向主进程发送消息,是子进程与主进程通信的出口。
  • process.on('message'):监听主进程发来的消息。
  • worker.send():主进程向子进程发送消息。
  • worker.on('exit'):监听子进程退出事件,用于资源回收。

spawn():通用进程创建方式

spawn() 更底层,适合启动任何系统命令或可执行文件,比如 ffmpegpython 脚本等。

// spawn-example.js
const { spawn } = require('child_process');

// 启动一个 Python 脚本
const pythonProcess = spawn('python', ['script.py', 'input.txt']);

// 监听标准输出
pythonProcess.stdout.on('data', (chunk) => {
  console.log('Python 输出:', chunk.toString());
});

// 监听标准错误
pythonProcess.stderr.on('data', (chunk) => {
  console.error('Python 错误:', chunk.toString());
});

// 监听进程结束
pythonProcess.on('close', (code) => {
  console.log('Python 进程结束,退出码:', code);
});

注释说明

  • spawn('python', ['script.py', 'input.txt']):启动 Python 解释器执行脚本。
  • stdout.on('data'):捕获子进程的标准输出(如 print 语句)。
  • stderr.on('data'):捕获错误输出。
  • close 事件在子进程退出时触发,可用于判断执行是否成功。

实战案例:构建一个并行任务调度器

现在我们来做一个实际项目:一个能并行处理多个 CPU 密集型任务的任务调度器。

// task-scheduler.js
const { fork } = require('child_process');

class TaskScheduler {
  constructor(workerCount = 4) {
    this.workers = [];
    this.taskQueue = [];
    this.workerCount = workerCount;
    this.initWorkers();
  }

  // 初始化多个子进程
  initWorkers() {
    for (let i = 0; i < this.workerCount; i++) {
      const worker = fork('./worker.js');
      
      // 为每个 worker 注册消息监听
      worker.on('message', (data) => {
        console.log(`任务完成 - 来自 worker ${i}:`, data.result);
        this.processNextTask();
      });

      // 注册退出事件
      worker.on('exit', (code) => {
        console.log(`Worker ${i} 退出,退出码: ${code}`);
        // 可以在这里重启 worker 或记录日志
      });

      this.workers.push(worker);
    }
  }

  // 添加任务到队列
  addTask(taskData) {
    this.taskQueue.push(taskData);
    this.processNextTask();
  }

  // 尝试分配任务给空闲的 worker
  processNextTask() {
    const availableWorker = this.workers.find(w => !w.isBusy);
    
    if (availableWorker && this.taskQueue.length > 0) {
      const task = this.taskQueue.shift();
      availableWorker.isBusy = true;
      availableWorker.send({ action: 'process', payload: task });
    }
  }

  // 优雅关闭所有 worker
  shutdown() {
    this.workers.forEach(worker => {
      worker.send({ action: 'shutdown' });
      worker.kill();
    });
  }
}

// 创建调度器实例
const scheduler = new TaskScheduler(4);

// 添加任务
scheduler.addTask({ type: 'sum', n: 1000000 });
scheduler.addTask({ type: 'fib', n: 50 });
scheduler.addTask({ type: 'prime', n: 10000 });

// 5 秒后关闭调度器
setTimeout(() => {
  scheduler.shutdown();
}, 5000);
// worker.js - 子进程处理任务
process.on('message', (msg) => {
  if (msg.action === 'shutdown') {
    console.log('收到关闭指令,停止工作');
    process.exit(0);
  }

  if (msg.action === 'process') {
    let result;

    switch (msg.payload.type) {
      case 'sum':
        result = sumToN(msg.payload.n);
        break;
      case 'fib':
        result = fibonacci(msg.payload.n);
        break;
      case 'prime':
        result = countPrimes(msg.payload.n);
        break;
      default:
        result = '未知任务';
    }

    // 将结果返回主进程
    process.send({ result, task: msg.payload.type });
  }
});

// 辅助函数:计算 1 到 n 的和
function sumToN(n) {
  let sum = 0;
  for (let i = 1; i <= n; i++) {
    sum += i;
  }
  return sum;
}

// 辅助函数:计算第 n 个斐波那契数
function fibonacci(n) {
  if (n <= 1) return n;
  let a = 0, b = 1;
  for (let i = 2; i <= n; i++) {
    [a, b] = [b, a + b];
  }
  return b;
}

// 辅助函数:统计小于 n 的质数个数
function countPrimes(n) {
  let count = 0;
  for (let i = 2; i < n; i++) {
    if (isPrime(i)) count++;
  }
  return count;
}

function isPrime(num) {
  if (num < 2) return false;
  for (let i = 2; i <= Math.sqrt(num); i++) {
    if (num % i === 0) return false;
  }
  return true;
}

注释说明

  • TaskScheduler 类封装了任务队列和 worker 管理逻辑。
  • isBusy 标记用于判断 worker 是否正在处理任务。
  • process.send() 用于将任务结果发回主进程。
  • worker.kill() 用于强制终止进程,适合优雅关闭场景。

多进程的优势与注意事项

使用 Node.js 多进程有诸多优势:

  • 避免阻塞:CPU 密集型任务不会阻塞事件循环。
  • 提高吞吐量:多个进程可并行处理任务,提升整体性能。
  • 稳定性增强:一个 worker 崩溃不会导致整个应用挂掉。

但也要注意以下几点:

项目 说明
内存开销 每个进程都有独立内存,创建过多会增加系统压力
通信开销 IPC 通信有延迟,频繁交互会影响性能
资源竞争 多进程访问共享资源(如文件)需加锁或协调
调试复杂度 多进程调试比单进程更复杂,需借助工具

建议:在实际项目中,根据 CPU 核心数合理设置 worker 数量(如 os.cpus().length),避免资源浪费。

总结:掌握 Node.js 多进程,构建高性能应用

Node.js 多进程不是“高深”的概念,而是解决实际性能瓶颈的有效手段。它让单线程的 Node.js 能够真正发挥多核 CPU 的潜力,尤其适合处理图像处理、数据计算、批量任务等场景。

通过 fork()spawn(),我们可以轻松创建子进程,实现任务拆分与并行处理。配合消息通信机制,主进程与子进程可以高效协作,构建出稳定、高性能的系统。

如果你正在开发一个需要处理大量计算或并发请求的应用,不妨尝试引入 Node.js 多进程机制。它不仅能提升响应速度,还能增强系统的容错能力。记住,真正的高性能,不在于单点爆发,而在于合理分工、协同作战。

从今天开始,让你的应用不再“卡住”,而是“飞起来”。