如何用 SharedArrayBuffer 与 Atomics.load 实现高效多 Worker 轮询

作者:袖梨 2026-06-13
Atomics.load 不适合轮询,应配合 Atomics.wait 使用;轮询会耗尽 CPU、阻塞线程且低效,而 wait/notify 零开销、毫秒响应,并需确保 crossOriginIsolated 环境。

Atomics.load 本身不适合轮询,强行用会吃光 CPU、卡死线程,且根本达不到“高效”——它只该用在配合 Atomics.wait 的同步链路里。

为什么不能用 Atomics.load 做轮询

轮询(polling)指 Worker 不停地调用 Atomics.load(view, index) 检查某个标志位是否变化。这看似简单,但问题严重:

  • 每次 Atomics.load 都是内存访问,无等待、无让出,纯空转 —— 在 4 核机器上可能把一个 Worker 线程跑满 100% CPU
  • 浏览器无法调度休眠,线程持续抢占时间片,影响其他 Worker 或主线程响应
  • 即使你加 setTimeoutawait new Promise(r => setTimeout(r, 1)),也变成低效假轮询,延迟不可控、唤醒不及时
  • Atomics.load 不触发任何通知机制,无法与生产者形成协作节奏,纯属单向瞎猜

真正高效的替代方案:Atomics.wait + notify

高效跨 Worker 协作不是“查”,而是“等”。Atomics.wait 让线程挂起,直到被明确唤醒,零 CPU 开销,毫秒级响应。

  • Worker 启动后,先调用 Atomics.wait(sharedView, 0, 0) —— 表示“等索引 0 变成非 0”
  • 主线程或上游 Worker 完成数据写入后,执行 Atomics.store(sharedView, 0, 1) 再紧跟着 Atomics.notify(sharedView, 0, 1)
  • 被唤醒的 Worker 拿到控制权,处理数据,完成后可重置标志(如 Atomics.store(sharedView, 0, 0)),再回到 wait
  • 注意:Atomics.wait 只能在 Worker 中安全调用;主线程调用会直接抛 TypeError

共享内存布局建议(避免踩坑)

别把所有状态塞进一个整数。多 Worker 协作时,内存布局混乱是竞态高发区:

  • 前 4 字节(Int32Array[0])专用于同步标志(0=空闲,1=就绪,2=忙)
  • 接下来 4 字节(Int32Array[1])存数据长度,防止越界读取
  • 剩余空间按需划分数据区,例如 new Float64Array(sab, 8) 存计算结果
  • 所有写入必须用 Atomics.store,所有读取必须用 Atomics.load —— 即使只是读长度,也不能直接 view[1]
  • 若需多个 Worker 并行写不同区域,确保它们操作的索引完全不重叠,否则仍需 Atomics.compareExchange 加锁

检查环境是否就绪再动手

代码写得再对,环境不达标就全白搭。运行前务必确认:

  • 服务端已返回两个关键 header:Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp
  • 页面中所有脚本、Worker 文件加载都带 crossorigin 属性,例如 <script src="worker.js" type="module" crossorigin></script>
  • 开发时不要双击打开 HTML,必须走 http://localhost:8080 类地址;file:// 协议下 self.crossOriginIsolated 永远为 false
  • 在 Worker 或主线程中打印 self.crossOriginIsolated,为 true 才能继续;否则 new SharedArrayBuffer() 会静默失败或报 SharedArrayBuffer is not defined

最容易被忽略的是:你以为自己在“轮询”,其实只是没意识到 Atomics.wait 才是唯一合理入口;而所有看似省事的“手动 sleep + load”组合,都在悄悄拖垮性能和可维护性。

相关文章

精彩推荐