如何利用 SharedArrayBuffer 配合 Atomics 来构建极致性能的跨线程协作模型

作者:袖梨 2026-07-26
必须满足跨域隔离、正确同步、内存布局可控三项前提才能用SharedArrayBuffer和Atomics构建高性能跨线程模型;否则将引发静默数据错乱或TypeError。

不能直接用 SharedArrayBufferAtomics 构建“极致性能”的跨线程协作模型——除非你已满足跨域隔离、正确同步、内存布局可控这三项硬性前提。跳过任一环节,性能不会提升,只会引入静默数据错乱或 TypeError: SharedArrayBuffer is not defined

SharedArrayBuffer 创建失败的常见报错和对应检查点

浏览器控制台报 SharedArrayBuffer is not definedAtomics is not defined,不是代码写错了,而是环境没达标:

  • 服务器响应头必须同时包含:Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp
  • <script><iframe><link rel="stylesheet"> 等所有跨源资源加载标签需显式加 crossorigin 属性(哪怕同域)
  • 本地开发不能双击 HTML 文件打开(file:// 协议被禁用),必须走 localhost127.0.0.1,例如 npx serve -p 8080
  • Chrome / Edge 105+、Firefox 93+、Safari 16.4+ 才完整支持;旧版 Safari 仅部分支持 Atomics.wait()

Atomics.wait() 不是 setTimeout,用错就卡死

Atomics.wait() 的行为常被误解为“让当前线程休眠 N 毫秒”,实际它只做一件事:在指定 Int32Array 索引位置的值**等于预期值**时,挂起当前线程,直到另一线程调用 Atomics.notify() 唤醒它。它不接受毫秒参数,也不保证唤醒时间。

  • 错误写法:Atomics.wait(view, 0, view[0], 1000) —— 第四个参数在多数浏览器中被忽略,且若 view[0] 已变,该调用立即返回 "not-equal",根本不会等
  • 正确模式:主线程写入 view[0] = 1 → Worker 检查 Atomics.load(view, 0) === 1 → 调用 Atomics.wait(view, 0, 1) → 主线程后续调用 Atomics.notify(view, 0, 1) 唤醒
  • 永远不要在无 notify 配合的场景下单独使用 wait,否则线程永久挂起,无法 debug

多线程计数器看似简单,但非原子操作必出错

以下两段代码执行 10000 次递增,在 4 个 Worker 并发下结果几乎必然不是 40000:

// ❌ 错误:非原子读-改-写int32View[0] = int32View[0] + 1;// ✅ 正确:单条原子指令完成Atomics.add(int32View, 0, 1);
  • 前者拆成三步:读取 → 计算 → 写入,中间可能被其他线程打断,造成丢失更新(lost update)
  • Atomics.add() 是 CPU 级原子指令映射,不可中断;类似地,Atomics.compareExchange() 适合实现自旋锁
  • 即使只读,也应优先用 Atomics.load(view, i) 而非 view[i],确保看到的是最新写入值(避免 CPU 缓存不一致)

WASM 多线程中 SharedArrayBuffer 的传递方式差异

在 WASM 场景下,SharedArrayBuffer 的使用路径和 JS Worker 不同,容易混淆:

  • Emscripten 编译时必须加 -pthread -s USE_PTHREADS=1,否则生成的 WASM 模块根本不识别共享内存
  • WASM 线性内存需声明为 (memory (shared 1 10)),即初始 1 页(64KB)、最大 10 页,且带 shared 标识
  • JS 主线程传给 WASM Worker 的不是 SharedArrayBuffer 对象本身,而是通过 Module._malloc() 分配的指针地址,由 WASM 自行映射到共享内存段
  • WASM 内部调用 __atomic_add_fetch 等内置函数,最终编译为对 Atomics.add 的调用,开发者无需手写 JS 层原子操作

最易被忽略的一点:共享内存没有自动垃圾回收机制。一旦创建 SharedArrayBuffer,它会一直驻留直到所有引用(包括 Worker 中的视图)全部释放。Worker 未正确 terminate() 或视图未置空,会导致内存泄漏,且无法被常规 DevTools 检测到。

相关文章

精彩推荐