跳到主要内容

Web Locks API 并发控制

TL;DR

Web Locks API 允许多个同源(same-origin)的标签页/iframe/worker 在访问同一份共享资源时做**互斥(exclusive)共享(shared)**协调,避免并发导致的数据损坏或竞态问题。它是浏览器侧的“轻量分布式锁”,常用于 IndexedDB、本地缓存、后台同步、跨标签页任务去重等场景。


解决什么问题

当你的网站在多个执行上下文同时运行时(例如两个标签页都打开了同一个应用),以下事情很容易发生:

  • 两个标签页同时跑数据迁移,导致重复写入或部分失败
  • 一个标签页在清理缓存,另一个正在读写同一份缓存
  • 多个 worker 同时执行“全局唯一”的后台任务(比如同步、刷新 token、重建索引)

Web Locks API 提供一个标准方式,让这些上下文在访问共享资源前先“排队”。


核心概念

  • Lock name:锁名(字符串)。同名锁之间会互斥/共享协调;不同名互不影响。
  • Mode
  • Scope:同源范围内生效(同协议 + 域名 + 端口)。跨域不共享锁。
  • Abort:可以用 AbortController 取消等待队列。

基本用法(互斥锁)

await navigator.locks.request("db-migration", async lock => {
// 这里的代码同一时刻只会在一个上下文中执行
await runMigrationIfNeeded();
});

要点:

  • request(name, callback) 在拿到锁后执行 callback,callback 返回/抛错都会自动释放锁。
  • callback 里参数 lock 通常不需要用到(它主要用于调试信息),把它当作“已经拿到锁”的信号即可。

共享锁 vs 互斥锁

共享锁适合“可以并发读,但写需要独占”的模式:

// 读:共享
await navigator.locks.request(
"cache-index",
{ mode: "shared" },
async () => {
return readIndex();
}
);

// 写:互斥
await navigator.locks.request(
"cache-index",
{ mode: "exclusive" },
async () => {
return rebuildIndex();
}
);

try-lock:不等待,拿不到就放弃

ifAvailable: true 做“尽量拿到”的模式,适合后台任务去重:

const result = await navigator.locks.request(
"background-sync",
{ ifAvailable: true },
async lock => {
if (!lock) return false; // 没拿到锁
await doSync();
return true;
}
);

if (!result) {
// 另一个标签页正在同步;本标签页不再重复执行
}

取消等待(AbortController)

const ac = new AbortController();

setTimeout(() => ac.abort(), 2000);

try {
await navigator.locks.request(
"long-queue-lock",
{ signal: ac.signal },
async () => {
// ...
}
);
} catch (e) {
// 等待锁期间被取消(或其它错误)
}

与 BroadcastChannel / localStorage 方案对比

传统方案常见做法:

  • localStorage 写入一个“锁标记”,靠 storage 事件通知其他标签页
  • BroadcastChannel 广播“我在做事了”,其他标签页自觉退让

它们的问题:

  • 需要自己处理过期、崩溃恢复、时钟漂移、抢占等复杂边界
  • 容易出现“两个都以为自己拿到了锁”的竞态

Web Locks API 的优势:

  • 浏览器原生协调等待队列与释放
  • 与页面生命周期更贴合(callback 结束自动释放)

常见实践模式

  1. 跨标签页任务去重
  2. IndexedDB 迁移
  3. 读写协调
  4. Service Worker + Page 协作

注意事项 / 限制

  • 仅在安全上下文(HTTPS 或 localhost)可用。
  • 锁是同源级别的“协作机制”,不提供跨设备/跨浏览器实例的分布式一致性。
  • 不适合当作安全边界:恶意脚本仍可忽略锁直接访问资源(它不是强制隔离)。
  • 需要考虑“回调执行很久”导致其他上下文长期等待:尽量把锁内逻辑做短、做可中断。

何时该用

适合:

  • 同源多上下文并发导致竞态的前端应用
  • 需要“全局唯一任务”的后台逻辑(同步、清理、刷新、迁移)

不适合:

  • 需要跨用户/跨设备的一致性(应使用服务端锁/队列)
  • 需要强安全隔离(应靠权限与后端约束)

参考

  • MDN: Web Locks API(建议从这里查看兼容性与示例)
  • 规范:W3C Web Locks API(更适合深入边界与语义)