You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Node.js Promise.all未并行执行CPU密集型任务?

问题:Node.js CPU密集型任务并行执行不符合预期

问题详情

编写了一段Node.js代码测试CPU密集型任务的多线程处理,但运行结果不符合预期,希望找出问题所在。

测试代码

const process = require('process');

const COUNT = 5000000000;

async function cpu_bound_task(n) {
    let pid = process.pid;
    let processName = process.title;
    console.log(`${pid} * ${processName} ---> Start counting...`);
    while(n>0){
        n--;
    }
    console.log(`${pid} * ${processName} ---> Finished counting...`);
}

async function test_multithreading_for_cpu_bound_tasks() {
    console.log("Without multithreading...");
    let start = new Date();
    await cpu_bound_task(COUNT);
    await cpu_bound_task(COUNT);
    let end = new Date();
    console.log('Time taken in seconds -', (end - start)/1000);

    console.log("With multithreading...");
    start = new Date();
    await Promise.all([
        cpu_bound_task(COUNT),
        cpu_bound_task(COUNT)
    ]);
    end = new Date();
    console.log('Time taken in seconds -', (end - start)/1000);
}

test_multithreading_for_cpu_bound_tasks();

实际输出

Without multithreading...
6956 * Windows PowerShell ---> Start counting...
6956 * Windows PowerShell ---> Finished counting...
6956 * Windows PowerShell ---> Start counting...
6956 * Windows PowerShell ---> Finished counting...
Time taken in seconds - 12.798
With multithreading...
6956 * Windows PowerShell ---> Start counting...
6956 * Windows PowerShell ---> Finished counting...
6956 * Windows PowerShell ---> Start counting...
6956 * Windows PowerShell ---> Finished counting...
Time taken in seconds - 12.763

预期输出

Without multithreading...
6956 * Windows PowerShell ---> Start counting...
6956 * Windows PowerShell ---> Finished counting...
6956 * Windows PowerShell ---> Start counting...
6956 * Windows PowerShell ---> Finished counting...
Time taken in seconds - 12.798
With multithreading...
6956 * Windows PowerShell ---> Start counting...
6956 * Windows PowerShell ---> Start counting...
6956 * Windows PowerShell ---> Finished counting...
6956 * Windows PowerShell ---> Finished counting...
Time taken in seconds - 12.763

用户疑问

猜测可能是一个线程占用了整个CPU导致第二个线程无法获取CPU资源,但设备有2个物理核心(4个虚拟核心),为何没有发生上下文切换?

补充测试(IO密集型任务)

若将cpu_bound_task修改为IO密集型任务,则能按预期并行执行:

function sleep(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

async function cpu_bound_task(n) {
    const SLEEP = 1000*5;
    let pid = process.pid;
    let processName = process.title;
    console.log(`${pid} * ${processName} ---> Start sleeping...`);

    await sleep(SLEEP);
    console.log(`${pid} * ${processName} ---> Finished sleeping...`);
}

问题分析与解决方案

核心问题:你没用到真正的多线程

Node.js的Promise.all和async/await只是单线程异步调度,不是多线程并行。

Node.js主线程是单线程的,CPU密集型任务会持续占用事件循环,完全不会让出执行权。哪怕用Promise.all包装两个任务,第一个任务的while循环会把主线程占满,直到它执行完毕,才会轮到第二个任务运行——本质还是串行执行,所以耗时和串行几乎一致,输出自然是先完成第一个再开始第二个。

而IO密集型任务能并行,是因为await sleep时,主线程会挂起当前任务,去处理另一个任务的console.log,等定时器触发后再回来执行后续代码,这是事件循环的异步调度逻辑,不是多线程并行。

正确实现:用worker_threads实现多线程并行

要真正利用多核CPU处理CPU密集型任务,必须使用Node.js的worker_threads模块,创建独立的工作线程来运行任务。

修改后的示例代码:

const { Worker, isMainThread, parentPort } = require('worker_threads');
const process = require('process');

const COUNT = 5000000000;

// 工作线程执行的CPU密集任务
function cpuBoundTask(n) {
    let pid = process.pid;
    let threadId = parentPort.threadId;
    console.log(`${pid} * Thread ${threadId} ---> Start counting...`);
    while(n>0){
        n--;
    }
    console.log(`${pid} * Thread ${threadId} ---> Finished counting...`);
}

if (!isMainThread) {
    // 工作线程接收主线程消息,执行任务后返回结果
    parentPort.on('message', (n) => {
        cpuBoundTask(n);
        parentPort.postMessage('done');
    });
} else {
    async function testMultithreadingForCpuBoundTasks() {
        console.log("Without multithreading...");
        let start = new Date();
        cpuBoundTask(COUNT);
        cpuBoundTask(COUNT);
        let end = new Date();
        console.log('Time taken in seconds -', (end - start)/1000);

        console.log("With multithreading...");
        start = new Date();
        // 创建两个工作线程
        const worker1 = new Worker(__filename);
        const worker2 = new Worker(__filename);

        // 向工作线程发送任务参数
        worker1.postMessage(COUNT);
        worker2.postMessage(COUNT);

        // 等待两个工作线程完成
        await Promise.all([
            new Promise(resolve => worker1.on('message', resolve)),
            new Promise(resolve => worker2.on('message', resolve))
        ]);

        end = new Date();
        console.log('Time taken in seconds -', (end - start)/1000);

        // 关闭工作线程
        worker1.terminate();
        worker2.terminate();
    }

    testMultithreadingForCpuBoundTasks();
}

补充说明

  • worker_threads属于同一个进程,所以PID和主线程一致,但每个工作线程有独立的线程ID,能真正利用多核CPU并行执行任务,耗时会接近串行的一半(取决于核心数量)。
  • Node.js的单线程模型专为IO密集型场景设计,CPU密集型任务必须通过多线程/多进程方式处理,否则会阻塞事件循环,导致整个应用响应缓慢。

内容的提问来源于stack exchange,提问作者Ankit Kumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 23:47:02