为何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
相关产品推荐
相关产品推荐

