Web Workers 与多线程并发
Site Owner
发布于 2026-05-21
系统讲解三种Worker类型、postMessage结构化克隆、Transferable零拷贝、SharedArrayBuffer+Atomics锁及线程池实现

Web Workers 与多线程并发
Dedicated Worker / Shared Worker / Service Worker 的职责划分;postMessage 的结构化克隆与 Transferable Objects 的零拷贝传输;SharedArrayBuffer + Atomics 的锁机制(Atomics.wait/notify);Worker 线程池的设计考量。
目录
- 为什么需要 Web Workers?
- 三种 Worker 的职责划分
- Dedicated Worker 深度解析
- postMessage 通信机制
- Transferable Objects 零拷贝
- SharedArrayBuffer + Atomics 共享内存
- 手写验证:线程池实现
- 深度追问
为什么需要 Web Workers?
JavaScript 主线程(UI 线程)同时负责:
- JS 执行
- 样式计算 & 布局
- 绘制 & 合成
- 用户交互响应
长时间 JS 运算会阻塞渲染,导致掉帧。
// ❌ 在主线程做大量计算:页面卡死
function expensiveSort(arr) {
return arr.sort((a, b) => a - b); // 百万级数组排序 → 阻塞 500ms+
}
// ✅ 丢给 Worker:主线程不卡顿
const worker = new Worker('sort.worker.js');
worker.postMessage(hugeArray);
worker.onmessage = (e) => renderResult(e.data);
Web Workers 的本质:独立的 JS 执行上下文(独立 EventLoop、独立堆),通过消息传递与主线程通信。
三种 Worker 的职责划分
| 类型 | 作用域 | 生命周期 | 主要用途 |
|---|---|---|---|
| Dedicated Worker | 单一页面 | 页面关闭时销毁 | CPU 密集计算 |
| Shared Worker | 同源多个页面 | 无连接时销毁 | 跨 Tab 共享状态 |
| Service Worker | 同源所有页面 | 独立于页面 | 离线缓存、网络代理 |
Worker 线程可用 API
// ✅ Worker 内可使用
self.postMessage() // 发消息给主线程
importScripts('lib.js') // 同步加载脚本(Dedicated Worker)
fetch / XMLHttpRequest
IndexedDB
WebSockets
Crypto API
FileReader
TextEncoder/TextDecoder
Performance API
setTimeout / setInterval
Promise / async-await
console.log
// ❌ Worker 内不可使用
document / window // 无 DOM
localStorage // 仅主线程可用(可用 IndexedDB 替代)
alert / confirm
canvas(部分浏览器支持 OffscreenCanvas)
Dedicated Worker 深度解析
基础使用
// ===== main.js =====
const worker = new Worker('./worker.js', {
type: 'module', // 支持 ES Module
name: 'myWorker', // 调试名称
});
worker.postMessage({ cmd: 'sort', data: [3, 1, 4, 1, 5, 9, 2, 6] });
worker.onmessage = (e) => {
console.log('Worker 返回:', e.data);
};
worker.onerror = (e) => {
console.error('Worker 错误:', e.message, e.filename, e.lineno);
e.preventDefault(); // 阻止错误冒泡到 window
};
worker.onmessageerror = (e) => {
console.error('消息反序列化失败:', e);
};
// 终止 Worker
worker.terminate();
// ===== worker.js =====
self.onmessage = (e) => {
{ cmd, data } = e.;
(cmd === ) {
sorted = [...data].( a - b);
self.({ : , : sorted });
}
};
self.(, { });
Inline Worker(无需单独文件)
function createInlineWorker(fn) {
const code = `(${fn.toString()})()`;
const blob = new Blob([code], { type: 'application/javascript' });
const url = URL.createObjectURL(blob);
const worker = new Worker(url);
URL.revokeObjectURL(url); // 创建后即可释放
return worker;
}
const worker = createInlineWorker(function() {
self.onmessage = (e) => {
const result = e.data * e.data;
self.postMessage(result);
};
});
worker.postMessage(10);
worker.onmessage = (e) => console.log(e.data); // 100
Module Worker
// main.js
const worker = new Worker('./worker.mjs', { type: 'module' });
// worker.mjs
import { heavyCompute } from './utils.mjs'; // 支持 ESM 导入!
self.onmessage = async (e) => {
const result = await heavyCompute(e.data);
self.postMessage(result);
};
postMessage 通信机制
结构化克隆算法(Structured Clone)
postMessage 使用结构化克隆序列化数据,支持的类型远多于 JSON.stringify:
// ✅ 支持的类型
worker.postMessage({
// 基本类型
num: 42,
str: 'hello',
bool: true,
nil: null,
// 特殊对象
date: new Date(),
regexp: /abc/gi,
map: new Map([['key', 'value']]),
set: new Set([1, 2, 3]),
error: new Error('test'),
// 二进制
buf: new ArrayBuffer(8),
u8: new Uint8Array([1, 2, 3]),
// 嵌套循环引用
// circRef: 支持循环引用!
});
// ❌ 不支持的类型(会抛出 DataCloneError)
worker.postMessage(function() {}); // 函数
worker.postMessage(document.body); // DOM 节点
worker.postMessage(());
worker.( ({}, {}));
双向通信封装(Promise 化)
// worker-bridge.js
class WorkerBridge {
#worker;
#pending = new Map();
#nextId = 0;
constructor(src) {
this.#worker = new Worker(src, { type: 'module' });
this.#worker.onmessage = (e) => this.#handleMessage(e);
this.#worker.onerror = (e) => this.#handleError(e);
}
call(method, ...args) {
const id = this.#nextId++;
return new Promise((resolve, reject) => {
this.#pending.set(id, { resolve, reject });
this.#worker.postMessage({ id, method, args });
});
}
#handleMessage({ data: { id, result, error } }) {
const pending = this.#pending.get(id);
if (!pending) return;
this.#pending.delete(id);
error ? pending.( (error)) : pending.(result);
}
#() {
( [id, { reject }] .#pending) {
( (e.));
}
.#pending.();
}
() { .#worker.(); }
}
handlers = {
: a + b,
: [...arr].( a - b),
: (n),
};
self. = {
{
result = handlers[method]?.(args);
self.({ id, result });
} (e) {
self.({ id, : e. });
}
};
bridge = ();
sum = bridge.(, , );
.(sum);
Transferable Objects 零拷贝
原理
普通 postMessage 拷贝数据(O(n) 时间复杂度)。对 Transferable 对象,只转移所有权指针(O(1)),原持有方的对象被 detach。
支持 Transferable 的类型
// ① ArrayBuffer
const buf = new ArrayBuffer(1024 * 1024 * 100); // 100 MB
worker.postMessage(buf, [buf]); // 第二参数:transfer 列表
console.log(buf.byteLength); // 0 ← 已被转移
// ② MessagePort(双工通道)
const { port1, port2 } = new MessageChannel();
worker.postMessage({ port: port2 }, [port2]);
port1.onmessage = (e) => console.log('主线程收到:', e.data);
port1.postMessage('ping');
// ③ OffscreenCanvas
const canvas = document.getElementById('myCanvas');
const offscreen = canvas.transferControlToOffscreen();
worker.postMessage({ canvas: offscreen }, [offscreen]);
// ④ ReadableStream / WritableStream(部分浏览器)
const [readable, writable] = new TransformStream().readable.tee();
// worker.postMessage({ stream: readable }, [readable]);
性能对比
const SIZE = 50 * 1024 * 1024; // 50 MB
const buf = new ArrayBuffer(SIZE);
// 测试:克隆 vs 转移
console.time('clone');
worker1.postMessage(buf); // 克隆:~50ms
console.timeEnd('clone');
const buf2 = new ArrayBuffer(SIZE);
console.time('transfer');
worker2.postMessage(buf2, [buf2]); // 转移:~0.1ms
console.timeEnd('transfer');
OffscreenCanvas 实战
// main.js
const canvas = document.getElementById('canvas');
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker('./canvas-worker.js');
worker.postMessage(
{ type: 'init', canvas: offscreen, width: 800, height: 600 },
[offscreen]
);
worker.postMessage({ type: 'draw', data: complexSceneData });
// canvas-worker.js
let ctx;
self.onmessage = ({ data }) => {
if (data.type === 'init') {
ctx = data.canvas.getContext('2d');
ctx.canvas.width = data.width;
ctx.canvas.height = data.height;
} else if (data.type === 'draw') {
renderComplexScene(ctx, data.data); // 不占用主线程
}
};
SharedArrayBuffer + Atomics 共享内存
基础同步原语
// main.js
const sab = new SharedArrayBuffer(4);
const arr = new Int32Array(sab);
Atomics.store(arr, 0, 0); // 初始值 0
const worker = new Worker('./atomic-worker.js');
worker.postMessage({ sab });
// 等待 Worker 完成(主线程 Atomics.wait 仅在 Worker 中可用!)
// 主线程应用 Atomics.waitAsync(ES2022)
const { async, value } = Atomics.waitAsync(arr, 0, 0);
if (async) {
value.then(() => console.log('Worker 已完成,值:', Atomics.load(arr, 0)));
}
// atomic-worker.js
self.onmessage = ({ data: { sab } }) => {
const arr = new Int32Array(sab);
for (let i = 0; i < 1_000_000; i++) {
Atomics.add(arr, 0, 1);
}
.(arr, , );
};
实现 Mutex(互斥锁)
class Mutex {
#lock;
static UNLOCKED = 0;
static LOCKED = 1;
constructor(sab, byteOffset = 0) {
this.#lock = new Int32Array(sab, byteOffset, 1);
}
lock() {
// 自旋等待直到 CAS 成功
while (true) {
const old = Atomics.compareExchange(
this.#lock, 0,
Mutex.UNLOCKED, // expected
Mutex.LOCKED // replacement
);
if (old === Mutex.UNLOCKED) return; // 加锁成功
// 加锁失败,等待解锁通知
Atomics.wait(this.#lock, 0, Mutex.LOCKED);
}
}
unlock() {
Atomics.store(this.#lock, 0, Mutex.UNLOCKED);
.(.#lock, , );
}
() {
.();
{ (); }
{ .(); }
}
}
RingBuffer(无锁生产者-消费者)
// 使用 Atomics 实现无锁环形缓冲区
class RingBuffer {
#buf; // SharedArrayBuffer
#head; // 读指针
#tail; // 写指针
#data;
constructor(capacity) {
// 布局:[head(4), tail(4), data(capacity * 4)]
this.#buf = new SharedArrayBuffer(8 + capacity * 4);
this.#head = new Int32Array(this.#buf, 0, 1);
this.#tail = new Int32Array(this.#buf, 4, 1);
this.#data = new Int32Array(this.#buf, 8, capacity);
this.capacity = capacity;
}
get sab() { return this.#buf; }
// 生产者调用
write(value) {
const tail = Atomics.load(this.#tail, 0);
const head = Atomics.load(this.#head, );
next = (tail + ) % .;
(next === head) ;
.(.#data, tail, value);
.(.#tail, , next);
.(.#head, , );
;
}
() {
head = .(.#head, );
tail = .(.#tail, );
(head === tail) ;
value = .(.#data, head);
.(.#head, , (head + ) % .);
value;
}
}
手写验证:线程池实现
完整线程池
// thread-pool.js
class ThreadPool {
#workers = [];
#taskQueue = [];
#freeWorkers = [];
#pending = new Map();
#taskId = 0;
constructor(workerSrc, size = navigator.hardwareConcurrency ?? 4) {
for (let i = 0; i < size; i++) {
this.#createWorker(workerSrc);
}
}
#createWorker(src) {
const worker = new Worker(src, { type: 'module' });
worker.onmessage = ({ data }) => this.#onWorkerMessage(worker, data);
worker.onerror = (e) => this.#onWorkerError(worker, e);
this.#workers.push(worker);
this.#freeWorkers.push(worker);
}
#onWorkerMessage(worker, { id, result, error }) {
const { resolve, reject } = this.#pending.get(id) ?? {};
this.#pending.delete(id);
this.#freeWorkers.push(worker); // 归还线程
error ? reject?.( (error)) : resolve?.(result);
.#();
}
#() {
.#workers = .#workers.( w !== worker);
.#freeWorkers = .#freeWorkers.( w !== worker);
worker.();
.#(worker.);
}
#() {
(.#taskQueue. === ) ;
(.#freeWorkers. === ) ;
worker = .#freeWorkers.();
{ id, task, transfer } = .#taskQueue.();
worker.({ id, ...task }, transfer ?? []);
}
() {
id = .#taskId++;
( {
.#pending.(id, { resolve, reject });
.#taskQueue.({ id, task, transfer });
.#();
});
}
() {
.(tasks.( .(t., t.)));
}
() { .#workers.; }
() { .#freeWorkers.; }
() { .#taskQueue.; }
() {
.#workers.( w.());
.#workers = [];
.#freeWorkers = [];
( { reject } .#pending.()) {
( ());
}
.#pending.();
}
}
handlers = {
() {
arr = (data);
arr.();
arr.;
},
() {
() { n <= ? n : (n - ) + (n - ); }
(n);
},
() {
data.( acc + v, );
},
};
self. = {
{
result = handlers[method]?.(buffer ?? args);
isBuffer = result ;
self.(
{ id, result },
isBuffer ? [result] : []
);
} (e) {
self.({ id, : e. });
}
};
pool = (, );
results = .(
.({ : },
pool.({ : , : + (i % ) })
)
);
buf = ( * );
(buf).( (a[i] = .() * | ));
sortedBuf = pool.(
{ : , : buf },
[buf]
);
.(, (sortedBuf).(, ));
pool.();
Worker 进度上报
// worker.js(支持进度上报)
self.onmessage = ({ data: { id, items } }) => {
const total = items.length;
let processed = 0;
for (const item of items) {
heavyProcess(item);
processed++;
if (processed % 1000 === 0) {
// 上报进度(progress 消息没有 id)
self.postMessage({ type: 'progress', id, progress: processed / total });
}
}
self.postMessage({ type: 'result', id, result: 'done' });
};
// main.js
worker.onmessage = ({ data }) => {
if (data.type === 'progress') {
progressBar.style.width = (data.progress * 100) + '%';
} else if (data.type === 'result') {
console.log('完成!', data.result);
}
};
深度追问
Q1:Shared Worker 和 Dedicated Worker 的应用场景区别?
Dedicated Worker 为单个页面服务,生命周期与创建页面绑定,适合 CPU 密集型计算(图像处理、加密运算、大数据排序)。
Shared Worker 可被同源多个页面/iframe 共享,通过 MessagePort 连接,适合跨 Tab 共享状态(如实时数据同步、WebSocket 连接复用)。注意:Shared Worker 的错误调试需在 chrome://inspect/#workers 中进行。
Q2:为什么 SharedArrayBuffer 需要 COOP/COEP 头?
2018 年 Spectre 漏洞披露后,攻击者可利用高精度计时器(performance.now)通过侧信道推断跨域内存内容。SharedArrayBuffer 可用于构建精度更高的计时器(共享内存自旋等待)。为此浏览器需要:
Cross-Origin-Opener-Policy: same-origin:隔离浏览上下文组,防止跨域窗口引用Cross-Origin-Embedder-Policy: require-corp:要求所有嵌入资源明确授权
满足这两个条件后,crossOriginIsolated 为 true,SharedArrayBuffer 才可用。
Q3:线程池应该设多少个 Worker?
理论上等于 CPU 核心数(navigator.hardwareConcurrency),避免线程切换开销。但需考虑:
- IO 密集型任务(fetch、IndexedDB)可适当增加(线程常处于等待状态)
- CPU 密集型任务等于核心数即可
- 留 1 个核心给主线程,防止主线程饥饿
Q4:Worker 内能使用 DOM 吗?如何操作 Canvas?
Worker 内无法访问 DOM(因为 DOM API 不是线程安全的)。但通过 OffscreenCanvas 可以在 Worker 中进行 Canvas 渲染:主线程调用 canvas.transferControlToOffscreen() 将控制权转移给 Worker,Worker 获得完整的 2D/WebGL 上下文,实现渲染不占用主线程。