【Node】用来处理CPU密集型任务的利器Worker Threads

分店老板的烦恼:佛跳墙太耽误事

分店老板每天忙得脚不点地,一边招待客人、一边下单、一边炒菜。

偏偏有位常客,每次来都点佛跳墙 。这东西光备料和炖就要好几个小时,而且得一直有人盯着火候、搅锅、加料(比如大量的循环完全的占用CPU那种)。

老板要是自己蹲厨房炖佛跳墙亲自不停地搅拌,那大厅里所有客人全得饿死。因为他是单线程------炖佛跳墙的时候没法同时接单。

分店老板想:要是有个帮我搅拌佛跳墙的学徒就好了,我让他去炖,好了叫我一声;我自己继续在前厅忙活,两不耽误。

这个学徒就是 Worker Threads


Worker Threads 就是分店里的专用学徒

  • 学徒有独立的脑子(线程):他可以独立干活,不干扰老板的思路。
  • 学徒有自己的灶台(独立内存):他炖糊了锅,不会让前台老板的锅也坏掉。
  • 学徒和老板有对讲机(消息通道) :通过 postMessage 告诉对方"材料""进度""炖好了"。
  • 学徒听老板指挥:老板说"去炖",他才开始;炖完报告"好了",老板端走。

最关键的区别 :学徒和老板共享同一个分店的营业执照(同一个进程),但他们占用不同的 CPU 核心,所以能并行干活。这点和 Cluster 开新分店不同------那是独立的店(独立进程)。


代码当场开演:老板和学徒

javascript 复制代码
// main.js ------ 分店老板(主线程)
const { Worker } = require('worker_threads');

console.log('老板:今天有客人点佛跳墙,叫学徒过来');

// 招一个学徒,告诉他工作脚本是 cook.js
const worker = new Worker('./cook.js');

// 给学徒发材料(传数据)
worker.postMessage({ dish: '佛跳墙', materials: ['鲍鱼', '海参', '花胶'] });

// 监听学徒的报告
worker.on('message', (msg) => {
    console.log(`老板收到:${msg}`); // "佛跳墙炖好了!"
    // 老板端出去给客人
});

worker.on('error', (err) => {
    console.log('老板:学徒把厨房炸了', err);
});

worker.on('exit', (code) => {
    console.log(`学徒收工,退出码:${code}`);
});

// 老板继续在前厅忙,不会被炖汤耽搁
console.log('老板继续接单、炒菜、收银...');
javascript 复制代码
// cook.js ------ 学徒的脚本(另一个线程执行)
const { parentPort } = require('worker_threads');

// 收到老板的指令
parentPort.on('message', (msg) => {
    console.log(`学徒:收到老板的材料,开始炖 ${msg.dish}`);

    // 模拟超级耗时的炖煮(这会阻塞学徒自己的线程,但不影响老板)
    const start = Date.now();
    while (Date.now() - start < 5000) {
        // 学徒在专心搅锅,不能分心
    }

    // 炖好了,通知老板
    parentPort.postMessage(`${msg.dish} 炖好了!`);
});

运行 main.js,你会看到:

复制代码
老板:今天有客人点佛跳墙,叫学徒过来
老板继续接单、炒菜、收银...
学徒:收到老板的材料,开始炖 佛跳墙
(5秒间隔,老板可以干其他事)
老板收到:佛跳墙 炖好了!
学徒收工,退出码:0

看,老板的代码一直没被阻塞。如果没有学徒,老板就得自己进去炖 5 秒,这期间别的客人全都干瞪眼。


共享内存:老板和学徒共用小白板

有时老板想随时知道"炖了多少了",学徒也需要看看新的配料清单,用 postMessage 一来一回太慢。

于是他们在墙上挂了块小白板(SharedArrayBuffer),两人都能看、都能写。

javascript 复制代码
// main.js 老板这边
const { Worker } = require('worker_threads');

// 一块 4 字节的小白板
const sab = new SharedArrayBuffer(4);
const view = new Int32Array(sab);

const worker = new Worker('./cook.js', { workerData: sab });
console.log('老板:小白板初始值', view[0]); // 0

// 老板偶尔去看看进度
setInterval(() => {
    console.log(`老板看一眼进度:${view[0]}%`);
    if (view[0] >= 100) {
        console.log('老板:学徒完工了,我去端菜');
        process.exit();
    }
}, 500);
javascript 复制代码
// cook.js 学徒这边
const { parentPort, workerData } = require('worker_threads');
const view = new Int32Array(workerData); // 拿到同一块白板

// 模拟炖煮,不断更新进度
let progress = 0;
function simmer() {
    if (progress >= 100) {
        parentPort.postMessage('完成');
        return;
    }
    progress += 10;
    view[0] = progress; // 写在白板上
    setTimeout(simmer, 300); // 炖一会儿
}
simmer();

这就实现了极低延迟的数据共享 ,但得小心死锁:两个人都写同一个地方容易冲突。所以一般用原子性 Atomics(好比两个人约定"我写字时你别写")来协调。


Worker Threads vs Cluster 对照表(连锁店版)

对比项 Cluster 分店 Worker Threads 学徒
比喻 开另一家分店,完全独立 店里的学徒,共用店面
进程/线程 独立进程 独立线程
内存 完全独立 可共享(SharedArrayBuffer)
通信 IPC(对讲机),消息序列化 postMessage + 共享内存
用途 分摊大量独立请求 处理 CPU 密集重活
崩溃影响 只挂一家店,其他照常 学徒搞砸可能影响整个进程(需隔离)
创建成本 高(新进程) 相对低(线程)

小吃店实战建议

  • 客人多(高并发 I/O):开连锁分店(Cluster)。
  • 佛跳墙多(CPU 密集重活):每个分店招学徒(Worker Threads)。
  • 简单小菜(普通异步):老板的事件循环足够。

分店老板终于不再担心被佛跳墙锁死,服务质量又上了一层。

相关推荐
不会敲代码14 小时前
RAG 进阶:从网页加载到智能文档分割
langchain·node.js
heyCHEEMS4 小时前
记录一下自动化构建中 SSE 与子进程管理的三个坑
javascript·node.js
qq_229058016 小时前
Volta的下载、安装使用教程
node.js
zh_xuan7 小时前
node.js搭建http服务
node.js
zhensherlock10 小时前
Protocol Launcher 系列:Working Copy 文件操作与高级命令详解
javascript·git·typescript·node.js·自动化·github·js
freewlt1 天前
VS Code 扩展开发:集成 GitHub Copilot 的完整指南
vscode·node.js
技术程序猿华锋1 天前
OpenAI GPT Image 2 教程:API Key 获取、参数说明与 Python/Node.js 示例
python·gpt·node.js·ai绘画
米丘1 天前
vue3.x 编译 script setup 编译过程
vue.js·node.js·babel