如何实现类方法运行时的带超时锁等待?现有轮询方案是否有更优解
当前实现方案的可行性判定
当前写法完全无法达到预期效果,存在根本性的设计缺陷:
- 函数虽然标注为async,但核心的while循环没有任何await让出执行权的操作,本质是同步阻塞的忙等逻辑。JS是单线程事件循环模型,一旦进入这个循环,主线程会被完全占死,所有后续的异步任务(包括负责把
this.locked设为false的解锁逻辑)都没有机会被调度执行。 - 逻辑上必然出现死锁:因为解锁代码永远排不上执行队列,不管等多久,循环里的
if (!this.locked)判断永远不会成立,最后100%会触发超时错误,根本等不到锁真正释放。 - 忙等逻辑会造成极高的CPU空转开销,哪怕等待的实际时间很短,循环也会持续高频执行时间戳查询,完全浪费计算资源。
更优的实现方案
核心逻辑是彻底放弃同步轮询的写法,基于Promise的异步挂起机制实现等待,在等待期间完全让出事件循环的执行权,直到收到锁释放的信号,或者超时再抛出错误。
首先需要在类内部补充基础的锁管理能力,不能仅靠一个locked布尔值做判断:
- 维护一个等待队列,存储所有等待锁释放的Promise的resolve触发器
- 配套实现标准的获取锁、释放锁的基础方法
类的基础结构示例:
class QueueManager { private locked = false // 存储等锁的回调触发器 private waitResolvers: Array<() => void> = [] // 内部获取锁的方法 private async acquireLock() { if (!this.locked) { this.locked = true return } // 已有锁时,将当前请求挂起,把resolve存入等待队列 await new Promise<void>((resolve) => { this.waitResolvers.push(resolve) }) // 被唤醒后立刻占用锁,避免多个请求同时进入临界区 this.locked = true } // 内部释放锁的方法 private releaseLock() { this.locked = false // 按入队顺序唤醒最早等待的请求 const nextResolver = this.waitResolvers.shift() nextResolver?.() } }
基于上述结构改造后的waitForUnlock方法:
async waitForUnlock(ms: number) { if (!this.locked) return await Promise.race([ // 等待锁释放的Promise new Promise<void>((resolve) => { this.waitResolvers.push(resolve) }), // 超时控制Promise new Promise((_, reject) => { setTimeout(() => { reject(new Error(`timeout ${ms} ms. The queue is locked.`)) }, ms) }) ]) }
这套方案的核心优势:
- 等待期间完全挂起异步逻辑,不会阻塞主线程,解锁操作可以正常被事件循环调度执行
- 没有CPU空转开销,只有当锁释放或者超时触发时,后续逻辑才会继续运行
- 支持多请求并发等锁,按入队顺序依次唤醒,不会出现竞态条件
实际使用的补充优化点:
- 可以把超时对应的定时器ID存下来,当锁在超时前释放时,主动调用
clearTimeout清理定时器,避免无效的定时器挂起造成内存泄漏 - 如果业务场景非常简单,实在不想维护等待队列,也可以在原有的循环逻辑里加入让出事件循环的操作,比如每次循环加
await new Promise(r => setTimeout(r, 50)),但这种轮询方案会带来额外的等待延迟、更高的调度开销,属于次选的简化方案,效率远不如基于Promise触发器的实现。
内容的提问来源于stack exchange,提问作者Krisztián Maurer
相关产品推荐
相关产品推荐

