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

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

相关推荐
niyongsheng14 小时前
后端零改动,给若依换一套现代化前端
vue.js·开源·node.js
liangshanbo121517 小时前
面试题:Webpack 的 publicPath 有什么作用?
前端·webpack·node.js
hasty1 天前
不上传新包,也能改变用户拿到的版本:npm dist-tag 的 OIDC 权限治理
前端·npm·node.js
福兮说2 天前
前后端算的 MD5、SHA-256 对不上?编码、换行、BOM、HMAC、JSON 顺序,八个原因逐个实测
前端·javascript·node.js·json·哈希算法
lingchen19062 天前
Node.js的下载安装配置
node.js
LRL_3 天前
【实战指南】Node.js 跨平台依赖下载:如何在 Windows/Linux 环境下互跨下载目标系统的 npm/pnpm 包
linux·windows·node.js
福兮说3 天前
URL 编码的七个坑:c++ 传到后端变空格、%25 套娃、截断 emoji 直接报错
开发语言·前端·javascript·node.js·url
liangshanbo12153 天前
Webpack vs Vite 底层原理高频追问的问题
前端·webpack·node.js
玖石书3 天前
HiSH 通过 apk 安装 Node.js、npm 与 pnpm
npm·node.js·鸿蒙·hish
池塘水悠悠3 天前
nodejs24安装(node.js安装)
node.js