一个轻量、无依赖的用户脚本互斥锁(mutex),通过 GM.setValue + GM_addValueChangeListener 进行协调,因此在任意时刻 只有一个标签页/上下文 会执行临界区代码。适用于共享同一用户脚本存储的多个标签页和 iframe。
安装 / @require 这是一个库,而不是独立脚本。请在你的用户脚本头部加入:
// @require https://scriptcat.org/lib/4709/0.1.0/GM_lock.js?sha384-dbz4ENOCrR50Xa7gxF6jsZLb8ExaYEMuOpTpDgw6Xt4yavf5ZgoPnbaoN4M3MIMQ
// JavaScript
var GM_lock: (tag: string, func: () => (void | Promise<void>)) => Promise<void>;
tag:用于标识锁作用域的字符串。相同 tag ⇒ 相同锁。
func:你的临界区(可同步或异步)。
返回:当 func 执行完成后 resolve 的 Promise<void>。
func 中的错误会被捕获并打印到控制台;它们不会导致 promise reject。注意: 当前实现不会返回
func的返回值(始终以void解析)。如果需要返回结果,请在func内写入外部变量或存储。
// 所有标签页中同一时刻只有一个实例会进入此代码块
await GM_lock('sync-cache', async () => {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
// 处理数据;按需持久化
await GM.setValue('cache:data', data);
});
按功能使用简短、稳定的 tag,例如:'queue:upload'、'cache:refresh'。
需要支持以下 API 的用户脚本管理器:
GM.setValueGM_addValueChangeListenerGM_removeValueChangeListener可选/条件需求(仅在启用 cleanup 时需要;见 Options):
GM.deleteValues(或旧版别名 GM_deleteValues)已在 ScriptCat、Tampermonkey、Violentmonkey 测试;其他支持上述 API 的环境理论上也可使用。
每个竞争者会构造一个 锁 id:id = <大时间戳>_<随机 base36>,并使用带有以下前缀的 按 tag 分组 键:
ack 键:GM_lock::<tag>::acknxt 键:GM_lock::<tag>::nxt竞争者通过写入 ack 来进行“宣布”,然后通过 nxt 中转。
同时竞争的伙伴会收集在同一 ack 时间点 写入的竞争者,经过一个小的 冲突间隔(默认约 500 ms)后,通过对收集的 id 排序并选择最小者,确定胜者。胜者进入 func。
执行完毕后,胜者再次更新 ack 以释放锁,若需要则允许下一轮选举。
这种事件驱动的设计能最小化轮询并在多个标签页/框架间进行良好协调。
COLLISION_INTERVAL = 500 —— 在选举胜者前的等待,用于减少竞争窗口。CLEANUP_VALUES = false —— 若设为 true,使用后会移除辅助键(需要 GM.deleteValues)。SHOW_LOG = false —— 若设为 true,通过 GM.setValue 输出时间标记(调试用)。func 中长时间阻塞事件循环;优先使用 await 异步工作。(其他竞争者依赖事件和短计时器进行协调。)func 内自行处理。公共领域 — The Unlicense。
本库在源码头部附带完整 Unlicense 声明,简要概括:
详情可参考:https://unlicense.org