2026年5月27日 12:42

Web Worker 性能优化:通信、并行与内存管理

先搞清楚瓶颈在哪

Worker 性能优化不是玄学,瓶颈就三个地方:创建开销通信开销计算开销。先 Profiler 看哪个是瓶颈,再对症下药,别瞎优化。

创建开销:复用比重建快 100 倍

new Worker() 不是免费的。浏览器要分配线程、解析脚本、初始化上下文,一次创建大概 10-50ms。如果你每次任务都新建再 terminate,开销比任务本身还大。

Worker 池

和数据库连接池一个道理——预先创建好,任务来了分配,做完了归还:

javascript
class WorkerPool { constructor(workerUrl, size = navigator.hardwareConcurrency || 4) { this.workers = []; this.queue = []; for (let i = 0; i < size; i++) { const worker = new Worker(workerUrl); worker.busy = false; worker.onmessage = (e) => { const { resolve } = worker.task; delete worker.task; worker.busy = false; this.processQueue(); resolve(e.data); }; this.workers.push(worker); } } exec(data) { return new Promise((resolve) => { const worker = this.workers.find(w => !w.busy); if (worker) { worker.busy = true; worker.task = { resolve }; worker.postMessage(data); } else { this.queue.push({ data, resolve }); } }); } processQueue() { if (this.queue.length === 0) return; const worker = this.workers.find(w => !w.busy); if (!worker) return; const { data, resolve } = this.queue.shift(); worker.busy = true; worker.task = { resolve }; worker.postMessage(data); } } // 使用 const pool = new WorkerPool('worker.js', 4); const result = await pool.exec({ type: 'sort', data: largeArray });

Worker 池适合任务频繁但单个任务不太大的场景。如果任务很少(比如页面生命周期内就跑一两次),直接 new Worker() 就行,别过度设计。

通信开销:序列化才是大头

Worker 通信的瓶颈不在网络,在序列化。postMessage 默认用结构化克隆,数据量大的时候拷贝耗时惊人。

Transferable:零拷贝传大数据

javascript
const buffer = new Float64Array(1_000_000); // 慢:结构化克隆,拷贝 8MB 数据 worker.postMessage({ data: buffer }); // 快:转移所有权,零拷贝 worker.postMessage({ data: buffer }, [buffer.buffer]); // 注意:转移后主线程不能再访问 buffer

实测数据:

数据大小结构化克隆Transferable
100KB~0.5ms~0.05ms
1MB~5ms~0.1ms
10MB~50ms~0.2ms

10MB 以上的数据,不用 Transferable 等于白用 Worker。

SharedArrayBuffer:跳过序列化

Transferable 虽然零拷贝,但只能单向传——发过去主线程就没了。如果你需要双向频繁读写同一块数据,用 SharedArrayBuffer:

javascript
const shared = new SharedArrayBuffer(1024 * 1024); const view = new Float64Array(shared); // 主线程和 Worker 共享同一块内存 worker.postMessage({ shared }); // Worker 里直接读写 self.onmessage = (e) => { const view = new Float64Array(e.data.shared); Atomics.store(view, 0, 42); // 原子写入 };

需要配合 Atomics 做原子操作,服务端还要配 COOP/COEP 头,门槛比 Transferable 高。但高频通信场景下收益巨大——完全没有序列化开销。

批量发送:减少通信次数

每秒 postMessage 100 次和 1 次发 100 条数据,后者快得多。序列化有固定开销(即使数据很小也要走一遍结构化克隆流程),减少次数比减少数据量更有效:

javascript
// 慢:逐条发送 data.forEach(item => worker.postMessage(item)); // 快:攒批发送 worker.postMessage({ batch: data });

计算开销:用多 Worker 并行

单 Worker 的计算速度和主线程 JS 一样,只是不卡 UI。要真正加速,得把任务拆给多个 Worker 并行跑:

javascript
function parallelSort(data, workerCount = 4) { const chunkSize = Math.ceil(data.length / workerCount); const chunks = []; for (let i = 0; i < workerCount; i++) { chunks.push(data.slice(i * chunkSize, (i + 1) * chunkSize)); } return Promise.all(chunks.map((chunk, i) => { return new Promise((resolve) => { const worker = new Worker('sort-worker.js'); worker.onmessage = (e) => resolve(e.data); worker.postMessage(chunk); }); })).then(sortedChunks => { // 合并已排序的分片 return mergeSortedArrays(sortedChunks); }); }

实测 100 万元素数组排序:

方案耗时
主线程单线程~800ms(UI 卡死)
单 Worker~800ms(UI 正常)
4 Worker 并行~250ms(UI 正常)

Worker 数量不要超过 CPU 核心数,navigator.hardwareConcurrency 可以拿到。多了反而会因为线程调度开销变慢。

内存管理

Worker 占的内存不会自动释放,必须显式 terminate()。如果页面生命周期内不再需要某个 Worker,立刻关掉:

javascript
// 任务完成后关闭 worker.onmessage = (e) => { handleResult(e.data); worker.terminate(); // 释放线程和内存 }; // 或者超时强制关闭 const timeout = setTimeout(() => worker.terminate(), 30000); worker.onmessage = (e) => { clearTimeout(timeout); handleResult(e.data); };

长时间运行的 Worker 要注意内存泄漏——Worker 里的闭包、事件监听器、定时器如果不用了不清理,内存会持续上涨。在 Worker 里加个定期自检:

javascript
setInterval(() => { const used = performance.memory?.usedJSHeapSize; if (used && used > 50 * 1024 * 1024) { // 超过 50MB self.postMessage({ type: 'memory-warning', used }); } }, 10000);

懒加载:按需创建 Worker

不是所有 Worker 都要在页面加载时就创建。用 new URL() + 动态 import 实现按需加载,首屏不需要的 Worker 等用到时再创建:

javascript
async function getWorker() { if (!workerInstance) { workerInstance = new Worker( new URL('./heavy-worker.js', import.meta.url), { type: 'module' } ); } return workerInstance; } // 用户点击"导出"按钮时才创建 button.onclick = async () => { const worker = await getWorker(); worker.postMessage(exportData); };

优化优先级

按收益从大到小排:

  1. Transferable 替代结构化克隆(大数据场景立竿见影)
  2. Worker 池复用(频繁创建销毁场景收益大)
  3. 批量发送减少通信次数(高频小消息场景)
  4. 多 Worker 并行(计算密集型场景)
  5. SharedArrayBuffer(超高频双向通信场景,门槛高但收益最大)
  6. 懒加载(首屏性能敏感场景)
标签:Web Worker