Shared Worker 是跨窗口协调的唯一可靠载体,需通过 Promise 缓存、SharedArrayBuffer 原子操作、clientId 路由隔离及主动清理机制防止脏写。

Shared Worker 本身不提供内置锁机制,但它是实现跨窗口协调的唯一可靠载体。防止多窗口写脏数据的关键,不是“加一个锁”,而是用 Shared Worker 构建一套状态可控、请求可串行、响应可复用的协作逻辑。
用请求级 Promise 缓存替代互斥锁
对同一资源(如 API 接口、文件下载 URL)的并发写操作,本质是多个页面同时发起相同请求导致服务端重复处理或响应错乱。解决方式不是阻塞写入,而是让后续请求等待首个请求的 Promise 完成:
- Shared Worker 内维护一个 Map<requestKey, Promise>,key 建议标准化为
JSON.stringify({ url, method, bodyHash }) - 收到新请求时,先查 Map:若存在未 resolve 的 Promise,直接返回该 Promise;否则新建 fetch,并把 Promise 存入 Map
- fetch 完成后统一 resolve/reject,自动通知所有等待者,避免多次提交或响应不一致
- 不依赖全局变量或 localStorage —— 它们无法跨 Worker 实例保持状态一致性
为关键字段设计原子化写保护
当多个窗口需协同更新共享状态(如计数器、用户在线状态、草稿内容),不能靠 Worker 全局变量赋值,而应使用细粒度的原子操作:
- 在 SharedArrayBuffer 中预留一段内存作为共享状态区,例如
const stateBuf = new SharedArrayBuffer(4096) - 用
Int32Array映射关键字段偏移,如const counter = new Int32Array(stateBuf, 0, 1) - 写入前用
Atomics.compareExchange(counter, 0, oldValue, newValue)检查并更新,失败则重试或退避 - 避免
Atomics.store()直接覆盖 —— 它不校验当前值,极易被其他窗口覆盖
用 clientId + 消息路由隔离写上下文
Shared Worker 不代表所有窗口能随意互写。必须明确“谁在写什么”,否则仍会脏写:
- 每个页面连接 Shared Worker 时,发送带唯一
clientId的初始化消息,Worker 记录 port 与 clientId 映射 - 写操作消息必须携带
clientId和目标资源标识(如resourceKey: "user-profile") - Worker 校验:若该 resourceKey 当前正被另一 clientId 持有写锁(可用 Atomics 标志位管理),则拒绝或排队
- 写完成后再广播结果,但只推送给订阅了该 resourceKey 的 clientId,不全量广播
页面关闭时自动清理持有状态
窗口意外关闭会导致锁滞留、Promise 悬停、请求卡死。必须主动防御:
- 每个页面建立连接后,Worker 保存其
port并监听port.onclose - 一旦触发,Worker 立即从等待队列中移除该 port 的所有 pending 请求
- 检查该页面是否持有写锁或 active 请求,若是,标记为超时并释放锁(如
Atomics.store(lockArray, idx, 0)) - 避免用
while (Atomics.load() === 1) {}自旋等待 —— 会耗尽 CPU,改用Atomics.wait()配合超时重试

















