概念定义
在读压倒写的负载下,互斥锁浪费并行性。读写锁拆分契约:读者共享、写者排他。语义简单,策略与性能却并不简单。
工作机制
状态跟踪读者计数与写者在位。读锁获取在同步下自增计数,写锁等待计数归零。到达策略决定饥饿行为:偏爱读者的不公平策略会让写者在持续读流量下无限等待。升级尝试存在竞态——两个读者同时请求写会死锁或失败,除非实现显式处理。
使用场景
读多写少、临界区不短、核数适中的数据。配置与缓存类场景应对比“原子指针交换不可变快照”——读者零锁成本,常更简单更快。
常见误区
“读写锁总比互斥快”不成立:临界区很短或核很多时,锁簿记与缓存行乒乓反而更慢。读锁递归会破坏若干实现的释放语义;写锁递归在常见实现里直接死锁。跨回调持读锁时,回调一旦写就死锁。锁字本身是争用点:每次读获取都弄脏缓存行,多核扩展性会塌——那正是 RCU 与 seqlock 的主场。
代码示例
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,
};
}
}
#!/usr/bin/env bash
set -euo pipefail
lock_file=${1:-"${TMPDIR:-/tmp}/devexamples-backup.lock"}
max_wait=${LOCK_WAIT_SECONDS:-5}
work_file=""
cleanup() {
local status=$?
if [[ -n "$work_file" && -f "$work_file" ]]; then
rm -f -- "$work_file"
fi
flock -u 9 2>/dev/null || true
exit "$status"
}
trap cleanup EXIT
exec 9>"$lock_file"
if ! flock -w "$max_wait" 9; then
printf 'could not acquire %s within %ss\n' "$lock_file" "$max_wait" >&2
exit 75
fi
printf 'lock acquired by pid %d\n' "$"
work_file="$(mktemp "${TMPDIR:-/tmp}/backup.XXXXXX")"
printf 'job %d started at %s\n' "$" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$work_file"
sleep "${JOB_SECONDS:-1}"
cat -- "$work_file"
printf 'job %d finished; lock released on exit\n' "$"