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 结束自动释放)
常见实践模式
- 跨标签页任务去重
- IndexedDB 迁移
- 读写协调
- Service Worker + Page 协作
注意事项 / 限制
- 仅在安全上下文(HTTPS 或 localhost)可用。
- 锁是同源级别的“协作机制”,不提供跨设备/跨浏览器实例的分布式一致性。
- 不适合当作安全边界:恶意脚本仍可忽略锁直接访问资源(它不是强制隔离)。
- 需要考虑“回调执行很久”导致其他上下文长期等待:尽量把锁内逻辑做短、做可中断。
何时该用
适合:
- 同源多上下文并发导致竞态的前端应用
- 需要“全局唯一任务”的后台逻辑(同步、清理、刷新、迁移)
不适合:
- 需要跨用户/跨设备的一致性(应使用服务端锁/队列)
- 需要强安全隔离(应靠权限与后端约束)
参考
- MDN: Web Locks API(建议从这里查看兼容性与示例)
- 规范:W3C Web Locks API(更适合深入边界与语义)