如何实现写者优先的读写自旋锁?
数据小许
结论
write_waiters 应在写者等待 wl 之前登记。当前实现先执行 acquire(&rwlk->wl),之后才增加等待写者数量。如果另一个写者正在竞争 wl,读者可能先拿到 wl,并在看到 write_waiters == 0 后进入。因此,这段实现无法可靠地阻止新读者越过等待中的写者。
readers 已经大于 0 的读者可以完成当前临界区。写者优先通常意味着有写者等待时,不再接纳新读者。
调整写者登记时机
可以先原子增加等待写者计数,再竞争 wl。等待读者退出期间,写者释放并重新获取 wl;持有 wl 并确认 readers == 0 后,再减少等待计数。写者在临界区期间继续持有 wl,直到释放写锁。
static void write_acquire_inner(struct rwspinlock *rwlk) {
__atomic_fetch_add(&rwlk->write_waiters, 1, __ATOMIC_SEQ_CST);
for (;;) {
acquire(&rwlk->wl);
if (__atomic_load_n(&rwlk->readers, __ATOMIC_SEQ_CST) == 0) {
break; // 持有 wl,且当前没有读者
}
release(&rwlk->wl);
}
// 仍持有 wl;从等待状态转为持有写锁
__atomic_fetch_sub(&rwlk->write_waiters, 1, __ATOMIC_SEQ_CST);
}
static void write_release_inner(struct rwspinlock *rwlk) {
release(&rwlk->wl);
}
读者获取锁时,仍应在持有 wl 的情况下检查等待写者计数,并在计数为零时增加 readers。读者释放锁时,原子减少 readers。这样,读者增加计数和写者检查读者数量都受 wl 保护;等待写者计数则会在写者竞争 wl 前变得可见。
读写两侧应对这些计数使用一致的原子操作,并按实验提供的接口正确初始化锁和计数器。__ATOMIC_SEQ_CST 使用较强的内存顺序;若要改用较弱顺序,需结合锁实现和内存模型单独验证。
自旋锁与 sleep/wakeup
自旋锁通过反复检查条件来等待,不会主动让出 CPU。sleep(channel, lock) 和 awake(channel) 用于阻塞式等待与唤醒。如果实验要求实现自旋锁,应按照实验提供的自旋锁接口忙等,避免把阻塞等待直接放进自旋锁临界区。锁实现是否允许调用这些函数,应以实验的接口约定为准。尤其要避免持有自旋锁时睡眠,否则可能造成死锁,或违反内核锁的使用规则。
备注:内容仅供参考。
