SharedArrayBuffer:解锁多线程真正共享内存的钥匙
SharedArrayBuffer 是实现 Web 多线程编程中主线程与 Worker 线程间真正内存共享的核心 API。它需要配合 Atomics 对象进行同步操作,满足跨域隔离安全策略,并通过 postMessage 的 transfer list 传递,从而实现高效、无拷贝的线程间通信。

你是否希望在 Web Worker 和主线程之间实现真正高效的内存共享,避免数据序列化与拷贝带来的性能损耗?SharedArrayBuffer 正是解决这一需求的关键技术。它允许 JavaScript 主线程与多个 Web Worker 线程直接读写同一块底层的 ArrayBuffer 内存区域,从而为高性能计算、实时音视频处理等场景提供了可能。然而,强大的能力也伴随着严格的使用规范。要安全有效地使用 SharedArrayBuffer,开发者必须掌握三个核心要点:依赖 Atomics API 确保线程安全访问,遵循跨域隔离策略以启用该功能,并正确使用转移列表进行内存传递。
SharedArrayBuffer 的创建与传递
创建一个 SharedArrayBuffer 对象是第一步,但它本身只是一个内存区域的引用。要实际读写其中的数据,必须通过 TypedArray 视图(如 Int32Array、Uint8Array 或 Float64Array)来进行。一个关键的注意事项是:当主线程需要将共享内存传递给 Worker 线程时,必须使用 postMessage 方法的“可转移对象”特性。如果忘记将 SharedArrayBuffer 对象放入 transfer list 参数中,浏览器将抛出错误,因为共享内存的所有权需要被明确转移。
- 主线程初始化:
const sab = new SharedArrayBuffer(1024); const ia = new Int32Array(sab); - 关键传递步骤:
worker.postMessage({data: ia}, [sab]);—— 注意,sab 必须明确出现在第二个参数的数组中。 - Worker 端接收:
onmessage = ({data: {data}}) => { const ia = data; };此时,Worker 中的 ia 视图指向的,就是和主线程同一块物理内存。
必须使用 Atomics 进行线程安全访问
在多线程并发环境下,直接使用如 ia[0] = 1 这样的赋值操作是极其危险的,因为它不是原子操作,可能被其他线程的读写操作打断,导致数据竞争和不可预知的结果。因此,所有对共享内存的读写及线程间的协调,都必须通过 Atomics 对象提供的一系列原子操作方法来完成,以确保操作的完整性和顺序性。
- 安全写入:
Atomics.store(ia, 0, 42)将值 42 原子性地存入 ia 索引 0 的位置。 - 安全读取:
Atomics.load(ia, 0)原子性地读取指定位置的值。 - 线程等待:
Atomics.wait(ia, 0, 0)让当前线程阻塞,直到 ia[0] 的值不再等于 0。 - 线程唤醒:
Atomics.notify(ia, 0, 1)唤醒最多 1 个在 ia[0] 上等待的线程。 - 原子运算:
Atomics.add(ia, 0, 1)先做加法,然后返回该位置的旧值,整个过程一气呵成。
启用 SharedArrayBuffer 的必要条件
出于安全考虑(主要是防范 Spectre 等基于时序的侧信道攻击),现代浏览器默认禁用了 SharedArrayBuffer 功能。要启用它,你的 Web 应用必须运行在“跨域隔离”的环境中。这是使用 SharedArrayBuffer 的强制性前提条件,无法绕过。
立即学习“前端免费学习笔记(深入)”;
- 服务器响应头:必须设置两个 HTTP 头:
Cross-Origin-Embedder-Policy: require-corp和Cross-Origin-Opener-Policy: same-origin。 - 资源加载策略:页面内所有跨域资源(脚本、iframe、图片等)都必须支持 CORS,即带有正确的
crossorigin属性。 - 环境验证:在浏览器控制台检查
self.crossOriginIsolated属性,只有当其值为true时,才能成功创建 SharedArrayBuffer。
典型协作模式:生产者-消费者循环
理解了基本原理后,如何在实战中应用呢?一个经典的多线程协作模式是生产者-消费者模型。例如,可以让一个 Web Worker 作为生产者进行密集计算,将结果写入共享内存;主线程作为消费者,读取并处理这些结果。
- 内存布局设计:通常,共享内存的前几个字节(例如4字节)用作状态标志位(0表示空闲,1表示数据就绪,2表示处理中),后续部分存储实际数据。
- Worker(生产者)流程:计算完成后,先调用
Atomics.store(sab, 0, 1)更新状态,再调用Atomics.notify(sab, 0)通知主线程。 - 主线程(消费者)流程:通过
Atomics.wait(sab, 0, 0)阻塞等待通知,被唤醒后使用Atomics.load读取数据,处理完毕后再用store将状态重置为 0。 - 核心优势:这种基于通知的机制,彻底避免了低效的轮询检查,能显著降低 CPU 占用。
