JavaScript运行在浏览器主线程中,密集计算任务会阻塞DOM渲染和用户交互。Web Worker API允许创建后台线程执行JavaScript,主线程与Worker线程通过消息传递通信。图像处理、大数据排序、加密计算等CPU密集型任务移至Worker后,主线程保持60fps流畅响应。本文讲解Web Worker的创建方式、通信机制、共享内存与线程池实践。
Web Worker线程模型与浏览器多线程机制
浏览器渲染进程包含多个线程:主线程负责DOM操作、事件处理、JavaScript执行;合成线程负责图层合成;解码线程处理音视频。Web Worker在渲染进程内创建独立的JavaScript运行环境,拥有自己的事件循环和全局对象,但无法访问DOM和window对象。
Worker分为三种类型:Dedicated Worker(专有Worker,与创建页面绑定)、Shared Worker(共享Worker,多个页面共享)、Service Worker(代理网络请求,用于PWA离线缓存)。本文重点讲解Dedicated Worker。
Worker创建与消息传递基础
创建Worker需要独立的JS文件,主线程通过Worker构造函数加载:
// main.js - 主线程
const worker = new Worker('worker.js');
// 发送消息到Worker
worker.postMessage({ command: 'sort', data: largeArray });
// 接收Worker返回的结果
worker.onmessage = (e) => {
console.log('排序完成:', e.data.result);
console.log('耗时:', e.data.elapsed, 'ms');
};
// 错误处理
worker.onerror = (e) => {
console.error('Worker错误:', e.message);
};
// 终止Worker
// worker.terminate();
// worker.js - Worker线程
self.onmessage = (e) => {
const { command, data } = e.data;
if (command === 'sort') {
const start = performance.now();
const result = data.sort((a, b) => a - b);
const elapsed = performance.now() - start;
self.postMessage({
result: result,
elapsed: elapsed
});
}
};
postMessage默认采用结构化克隆算法复制数据,大对象传输有性能开销。对于二进制数据(ArrayBuffer),使用Transferable Objects实现零拷贝转移:
// 主线程:Transferable转移ArrayBuffer所有权
const buffer = new ArrayBuffer(1024 * 1024 * 10); // 10MB
worker.postMessage({ buffer }, [buffer]);
// 转移后主线程的buffer变为detached,无法再访问
// Worker线程接收后直接拥有该buffer
self.onmessage = (e) => {
const view = new Uint8Array(e.data.buffer);
// 处理数据...
self.postMessage({ buffer: view.buffer }, [view.buffer]);
};
SharedArrayBuffer与Atomics共享内存
Transferable Objects转移后原线程失去访问权。SharedArrayBuffer允许多线程同时访问同一块内存,配合Atomics API实现线程同步:
// 主线程创建共享内存
const sharedBuffer = new SharedArrayBuffer(4 * 1024 * 1024); // 4MB
const sharedArray = new Int32Array(sharedBuffer);
const worker = new Worker('worker-shared.js');
worker.postMessage({ buffer: sharedBuffer });
// 主线程写入数据
sharedArray[0] = 42;
Atomics.notify(sharedArray, 0, 1); // 唤醒等待的Worker
// worker-shared.js
self.onmessage = (e) => {
const sharedArray = new Int32Array(e.data.buffer);
// 等待主线程写入
Atomics.wait(sharedArray, 0, 0);
console.log('收到:', Atomics.load(sharedArray, 0));
// 原子操作:累加
Atomics.add(sharedArray, 1, 1);
Atomics.notify(sharedArray, 1, 1);
};
SharedArrayBuffer要求页面设置COOP和COEP响应头,否则浏览器禁用:
# Nginx配置
add_header Cross-Origin-Opener-Policy "same-origin";
add_header Cross-Origin-Embedder-Policy "require-corp";
Worker池管理与任务调度
频繁创建销毁Worker有开销,生产环境使用Worker池复用线程。以下是一个简单的Worker池实现:
class WorkerPool {
constructor(workerUrl, poolSize = navigator.hardwareConcurrency || 4) {
this.workers = [];
this.queue = [];
this.busy = new Set();
for (let i = 0; i < poolSize; i++) {
const worker = new Worker(workerUrl);
worker.addEventListener('message', (e) => {
worker._resolve(e.data);
worker._resolve = null;
this.busy.delete(worker);
this.dispatch();
});
this.workers.push(worker);
}
}
exec(data, transferList = []) {
return new Promise((resolve) => {
this.queue.push({ data, transferList, resolve });
this.dispatch();
});
}
dispatch() {
if (this.queue.length === 0) return;
const idle = this.workers.find(w => !this.busy.has(w));
if (!idle) return;
const task = this.queue.shift();
this.busy.add(idle);
idle._resolve = task.resolve;
idle.postMessage(task.data, task.transferList);
}
destroy() {
this.workers.forEach(w => w.terminate());
}
}
// 使用Worker池并行处理图像
const pool = new WorkerPool('image-worker.js', 4);
// 并行处理4张图片
const tasks = imageFiles.map(file => pool.exec({ file }));
const results = await Promise.all(tasks);
实际场景:大数据排序与图像处理
以下示例展示Worker处理100万元素排序,主线程同时保持UI响应:
// 生成100万随机数
const data = Array.from({ length: 1000000 }, () => Math.random());
// Worker排序,主线程不阻塞
const worker = new Worker('sort-worker.js');
worker.postMessage({ data }, []);
// 用户仍可流畅操作页面
button.onclick = () => {
document.body.style.background = '#f0f0f0'; // 立即响应
};
worker.onmessage = (e) => {
progressBar.value = 100;
console.log('排序完成,耗时:', e.data.time + 'ms');
};
Worker线程中也可创建子Worker(Chained Worker),实现流水线并行。但Worker嵌套层级不宜过深,通常2-3层即可。Worker调试需在Chrome DevTools的Sources面板中查看独立线程上下文,断点和日志与主线程分开显示。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/webworker-duo-xian-cheng-shi-zhan-hou-tai-ji-suan-yu-xian/