【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)。
  • 简单小菜(普通异步):老板的事件循环足够。

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

相关推荐
凌云拓界15 小时前
状态机与思考循环 ——CogitoAgent开发实战(一)
javascript·人工智能·架构·node.js·设计规范
winfredzhang18 小时前
用 Node.js + SQLite + 原生前端写一个本地情绪急救 Web App:情绪降落伞 Mood Parachute
前端·sqlite·node.js·express·情绪管理
不好听61318 小时前
Bun vs Node.js:谁才是 TypeScript 的"亲爹"?
typescript·node.js·bun
矩阵科学18 小时前
Langchain.js 实战四:工具的使用
langchain·node.js
前端双越老师20 小时前
2026 年从 0 开发 AI Agent 需要的 10 个技能
node.js·agent·ai编程
夜雪闻竹20 小时前
版本管理:npm 发布 + Electron 打包 + CI/CD
ci/cd·npm·node.js·代码规范·chatcrystal
winfredzhang1 天前
Node.js + SQLite 实战:本地 Markdown 阅读书架源码深度解析
sqlite·node.js·safari·分页·多媒体·md文档
码云之上1 天前
聊聊如何设计一个高效、稳定的 Node.js 接入层
前端·后端·node.js
To_OC2 天前
折腾两天 HTTP 接口调用,终于把 fetch 和前后端分离从书本概念落地到实操了
javascript·node.js·全栈
zhangfeng11332 天前
workbuddy ,node.js 每次会在 项目目录上安装 node_modules,能不能一次安装多次使用,为什么 npm 不把包装在全局
前端·npm·node.js