功能说明
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,
};
}
}