一、先抛结论,再讲原理
CPU 密集 + 可拆分 = 多 Worker 并行;IO 密集就别折腾线程,异步事件循环足够。
记住这句话,下面所有内容都是它的推导过程 + 实战 demo。
二、30 s 看懂 CPU 密集 vs. IO 密集
| 类型 | 瓶颈 | 典型场景 | 主线程阻塞 | 上 Worker 收益 | |||||| | CPU 密集 | 运算单元打满 | 加解密、图像滤镜、哈希、物理模拟 | ✅ 会卡 | 极大(卡→不卡) | | IO 密集 | 等网络/磁盘 | fetch、IndexedDB、文件读写 | ❌ 不卡 | ≈ 0(等待时间不变) |
一句话:
“计算型才值得开线程,等待型靠异步回调就够。”
三、二维决策表:把“拆不拆”画成坐标系
横轴:CPU or IO
纵轴:可拆分 or 不可拆分
| | CPU | IO | |||| | 可拆分 | ⭐⭐⭐ 多 Worker 并行 | 异步事件循环即可 | | 不可拆分 | ⭐ 单 Worker 也行 | 异步事件循环即可 |
左上角(CPU+可拆分) 才是 Worker 的“甜蜜区”。
其余三个象限,要么没必要,要么收益有限。
四、实战套路:4 步公式
遇到任何重活,按顺序打钩:
-
是不是 CPU 密集?
├─ 否 → 直接异步 IO,pass
└─ 是 → 下一步 -
能不能拆?(数据/算法无关性)
├─ 否 → 单 Worker 丢进去,至少不卡 UI
└─ 是 → 下一步 -
拆多少份?
→const POOL = navigator.hardwareConcurrency - 1
留 1 核给主线程谈恋爱(渲染),其余全上 Worker 池。 -
通信大不大?
→ 每块 <1 MB 直接postMessage
→ 大块用Transferable或SharedArrayBuffer,避免克隆开销。
五、完整案例:2 GB 大文件多线程哈希
需求:前端算 2 GB 视频的 SHA-256,不能卡。
| 指标 | 单线程 | 7 Worker | |||| | 总耗时 | 25 s | 4 s | | 主线程占用 | 100 % 卡死 | <5 ms 丝滑 | | 进度条 | 无 | 实时 0→100 % |
Step-by-step:
-
拆块
块大小 2 MB → 1024 块
Worker 池 = 7(8 核 CPU -1) -
并行
主线程轮询空闲 Worker,投递“块号 + 块数据”
Worker 内crypto.subtle.digest计算 SHA-256 -
合并
收到 1024 个块哈希后,主线程一次性 Merkle Tree 归并,吐出最终哈希 -
通信优化
块数据使用ArrayBuffer+transfer,零拷贝;进度使用postMessage传数字,<1 ms
六、速查表:日常任务对号入座
| 任务 | 类型 | 可拆分 | 决策 | ||||| | 图像高斯模糊 | CPU | ✅ | 多 Worker 按行分片 | | 视频解码 | CPU+IO | ✅ | Worker 解码,主线程只渲染 | | 排序 100 万条记录 | CPU | ✅ | 归并排序拆→多 Worker→归并 | | IndexedDB 批量写 | IO | ✅ | 不需要 Worker,事务异步即可 | | AES 加密大文件 | CPU | ✅ | 分块并行加密 | | 调用后端 API | IO | ❌ | fetch 直接异步,别放 Worker |
七、代码模板:拿来即用
主线程 main.js
import { spawn, Pool, Worker } from 'threads';
const pool = Pool(() => spawn(new Worker('./hash-worker.js')), {
size: navigator.hardwareConcurrency - 1
});
async function calcFileHash(file) {
const chunkSize = 2 * 1024 * 1024;
const chunks = Math.ceil(file.size / chunkSize);
const promises = [];
for (let i = 0; i < chunks; i++) {
const slice = file.slice(i * chunkSize, (i + 1) * chunkSize);
promises.push(pool.queue(worker => worker(slice)));
}
const hashes = await Promise.all(promises);
return mergeMerkle(hashes); // 主线程轻量合并
}
Worker hash-worker.js
import { expose } from 'threads/worker';
import crypto from 'crypto'; // 浏览器即 crypto.subtle
expose(async function (chunk) {
const buf = await chunk.arrayBuffer();
const hash = await crypto.subtle.digest('SHA-256', buf);
return new Uint8Array(hash);
});
八、避坑 3 连
-
线程别泛滥
创建销毁一次 2~5 ms,手机 8 核顶格,超了反而抢占主线程。 -
通信别裸克隆
1 MB 数据postMessage耗时 1~10 ms,大图直接transfer。 -
调试别懵逼
DevTools → Sources → Workers 面板,断点、console 一应俱全。
九、一句话总结
“CPU 密集 + 可拆分” 才配让主线程“下班”,其余情况异步事件循环就能搞定。
下次任何重活,先规划,再写代码——
让你的页面从此“假死”归零,丝滑到用户怀疑人生。