← 中文资源

简体中文版本

异步任务读写锁

实现写优先的异步读写锁,支持多个并发读者、独占写者、单次释放与等待者计数。

阅读英文原文

功能说明

ReadWriteLock 允许多个读者同时进入,但写者必须独占,且等待中的写者会阻止新读者插队,避免写饥饿。acquireRead 与 acquireWrite 返回释放函数,可安全地在 finally 中调用。适合缓存刷新、配置热更新、文件读写和内存索引重建。state 暴露读者数、写者状态与两类等待队列长度,便于指标监控和测试。

关键代码

读者进入时检查无写者且无等待写者,满足则直接增加计数;否则进入等待队列。写者只有在完全空闲且无人排队时立即进入,否则排队。释放读者时计数减一,当归零且有等待写者时,先把写者状态置为真再唤醒队首,实现所有权预留。释放写者时优先唤醒下一个写者,没有写者才一次性唤醒全部读者并预先增加读者计数。

使用注意

锁只在单进程内有效,多进程或多副本需要分布式锁。持锁期间应避免无界等待的 I/O,必要时给业务操作加超时并在 finally 释放。释放函数做了幂等保护,重复调用不会破坏计数。写优先策略在写请求持续不断时可能让读者等待较久,需结合业务权衡。不要在持锁期间递归获取写锁,否则会死锁。

验证方式

测试多个读者并发进入、写者等待读者全部释放、读者在写者等待时排队。验证写者释放后优先唤醒下一个写者,全部写者完成后读者批量进入。让任务抛错并调用释放,检查计数恢复。重复调用释放函数,断言状态不变;测试读写交替压力场景下最终计数归零且无死锁。

代码示例

export type Release = () => void;

export class ReadWriteLock {
  private readers = 0;
  private writer = false;
  private waitingReaders: Array<() => void> = [];
  private waitingWriters: Array<() => void> = [];

  async acquireRead(): Promise<Release> {
    if (!this.writer && this.waitingWriters.length === 0) {
      this.readers += 1;
      return this.createReadRelease();
    }
    await new Promise<void>((resolve) => this.waitingReaders.push(resolve));
    return this.createReadRelease();
  }

  async acquireWrite(): Promise<Release> {
    if (!this.writer && this.readers === 0 && this.waitingWriters.length === 0) {
      this.writer = true;
      return this.createWriteRelease();
    }
    await new Promise<void>((resolve) => this.waitingWriters.push(resolve));
    return this.createWriteRelease();
  }

  private createReadRelease(): Release {
    let released = false;
    return () => {
      if (released) return;
      released = true;
      this.readers -= 1;
      if (this.readers === 0 && this.waitingWriters.length > 0) {
        this.writer = true;
        this.waitingWriters.shift()!();
      }
    };
  }

  private createWriteRelease(): Release {
    let released = false;
    return () => {
      if (released) return;
      released = true;
      this.writer = false;
      if (this.waitingWriters.length > 0) {
        this.writer = true;
        this.waitingWriters.shift()!();
        return;
      }
      const readers = this.waitingReaders.splice(0);
      this.readers += readers.length;
      for (const resolve of readers) resolve();
    };
  }

  get state(): {
    readers: number;
    writer: boolean;
    waitingReaders: number;
    waitingWriters: number;
  } {
    return {
      readers: this.readers,
      writer: this.writer,
      waitingReaders: this.waitingReaders.length,
      waitingWriters: this.waitingWriters.length,
    };
  }
}