Node.js 利用多进程优化 CPU 密集型计算

作者:岩茶时间:2026-10-23 07:41:19浏览:561

Node.js 单线程模型在处理长时间运算时易导致 CPU 阻塞,不适合 CPU 密集型应用。本文通过斐波那契数列计算示例,对比单线程与基于 Cluster 模块的多进程方案。结果显示,利用多核 CPU 创建多个子进程并行处理,可将执行时间从 44 秒缩短至 10 秒左右,显著提升处理效率。

Node.js 单线程与多进程

Node.js 单线程与多进程

Node.js 凭借异步事件驱动和非阻塞 I/O 机制,在性能方面表现优异,因此被广泛采用。然而,其单线程架构也存在明显局限:当执行长时间运算时,CPU 无法及时释放,导致程序响应变慢,因此 Node.js 并非处理 CPU 密集型任务的最佳选择。

尽管 Node.js 不支持多线程,但可以通过创建多个子进程来分担任务。Node.js 内置了 child_process 和 cluster 两个模块,可用于实现多子进程并发执行。

单线程与多进程性能对比测试

接下来,我们将分别使用单线程和多进程两种方式,模拟大量斐波那契数的查找计算,以此进行 CPU 密集型测试。

以下代码用于查找第 35 位的斐波那契数,共执行 500 次。选择该位置是为了确保测试时间既不会过短导致误差,也不会过长影响体验。

单线程处理

代码文件:single.js


function fibonacci(n) {
 if (n == 0 || n == 1) {
  return n;
 } else {
  return fibonacci(n - 1) + fibonacci(n - 2);
 }
}

let startTime = Date.now();
let totalCount = 500;
let completedCount = 0;
let n = 35;

for (let i = 0; i < totalCount; i++) {
 fibonacci(n);
 completedCount++;
 console.log(`process: ${completedCount}/${totalCount}`);
}
console.log("👏 👏 👏 👏 👏 👏 👏 👏 👏 👏");
console.info(`任务完成,用时: ${Date.now() - startTime}ms`);
console.log("👏 👏 👏 👏 👏 👏 👏 👏 👏 👏");

执行命令 node single.js 查看运行结果。

在我的电脑配置下,运行结果显示耗时为 44611 毫秒(具体数值因电脑配置不同而异)。

... process: 500/500 👏 👏 👏 👏 👏 👏 👏 👏 👏 👏 任务完成,用时: 44611ms 👏 👏 👏 👏 👏 👏 👏 👏 👏 👏

查找 500 次耗时约 44 秒,速度较慢。可以预见,如果计算位置更大或任务数量更多,耗时将成倍增加。

接下来我们尝试使用多进程方案进行优化 ⬇️

基于 Cluster 模块的多进程实现

多进程

采用 cluster 模块,基于 Master-Worker 模式进行测试。

项目包含 3 个 JS 文件:主线程代码 master.js、子进程代码 worker.js 以及入口代码 cluster.js(入口文件并非必须单独编写,此处为了结构清晰而独立存在)。

主线程代码:master.js


const cluster = require("cluster");
const numCPUs = require("os").cpus().length;

// 设置子进程执行程序
cluster.setupMaster({
 exec: "./worker.js",
 slient: true
});

function run() {
 // 记录开始时间
 const startTime = Date.now();
 // 总数
 const totalCount = 500;
 // 当前已处理任务数
 let completedCount = 0;
 // 任务生成器
 const fbGenerator = FbGenerator(totalCount);

 if (cluster.isMaster) {
  cluster.on("fork", function(worker) {
   console.log(`[master] : fork worker ${worker.id}`);
  });
  cluster.on("exit", function(worker, code, signal) {
   console.log(`[master] : worker ${worker.id} died`);
  });

  for (let i = 0; i < numCPUs; i++) {
   const worker = cluster.fork();

   // 接收子进程数据
   worker.on("message", function(msg) {
    // 完成一个,记录并打印进度
    completedCount++;
    console.log(`process: ${completedCount}/${totalCount}`);

    nextTask(this);
   });

   nextTask(worker);
  }
 } else {
  process.on("message", function(msg) {
   console.log(msg);
  });
 }

 /**
  * 继续下一个任务
  *
  * @param {ChildProcess} worker 子进程对象,将在此进程上执行本次任务
  */
 function nextTask(worker) {
  // 获取下一个参数
  const data = fbGenerator.next();
  // 判断是否已经完成,如果完成则调用完成函数,结束程序
  if (data.done) {
   done();
   return;
  }
  // 否则继续任务
  // 向子进程发送数据
  worker.send(data.value);
 }

 /**
  * 完成,当所有任务完成时调用该函数以结束程序
  */
 function done() {
  if (completedCount >= totalCount) {
   cluster.disconnect();
   console.log("👏 👏 👏 👏 👏 👏 👏 👏 👏 👏");
   console.info(`任务完成,用时: ${Date.now() - startTime}ms`);
   console.log("👏 👏 👏 👏 👏 👏 👏 👏 👏 👏");
  }
 }
}

/**
 * 生成器
 */
function* FbGenerator(count) {
 var n = 35;
 for (var i = 0; i < count; i++) {
  yield n;
 }
 return;
}

module.exports = {
 run
};

1. 子进程数量根据当前电脑的逻辑 CPU 核数动态创建,不同设备核数不同。我的 CPU 拥有 6 个物理核,支持超线程技术,因此逻辑核数为 12,程序会相应创建 12 个子进程。

2. 主线程与子进程之间的通信通过 send 方法发送数据,并监听 message 事件来接收子进程返回的结果。

3. 代码中使用了 ES6 的 Generator 生成器来模拟生成每次需要查找的斐波那契数位置(虽然此处为固定值,但为了与单线程测试保持一致)。这样做的目的是避免一次性将所有任务抛出导致阻塞,而是由程序端控制,完成一个任务再分发下一个,实现“完成一个,放一个”的节奏控制。

子进程代码:worker.js


function fibonacci(n) {
 if (n == 0 || n == 1) {
  return n;
 } else {
  return fibonacci(n - 1) + fibonacci(n - 2);
 }
}

// 接收主线程发送过来的任务,并开始查找斐波那契数
process.on("message", n => {
 var res = fibonacci(n);
 // 查找结束后通知主线程,以便主线程再度进行任务分配
 process.send(res);
});
入口代码:cluster.js
// 引入主线程js,并执行暴露出来的run方法
const master = require("./master");
master.run();

执行命令 node cluster.js 查看运行结果。

在我的电脑配置下,运行结果显示耗时为 10724 毫秒(具体数值因电脑配置不同而异)。

process: 500/500 👏 👏 👏 👏 👏 👏 👏 👏 👏 👏 任务完成,用时: 10724ms 👏 👏 👏 👏 👏 👏 👏 👏 👏 👏

结果

通过上述两种方式的对比,结果显而易见:多进程处理速度是单线程的 4 倍多。此外,在硬件条件允许且 CPU 核心数充足的情况下,增加进程数量可以进一步提升处理速度。

如果有更优的方案或其他语言能更好地满足需求,那自然是更好的选择。毕竟 Node.js 天生就不太适合 CPU 密集型应用。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

作者最新文章