不能直接用 std::atomic 模拟读写锁,因其缺乏等待和条件阻塞能力,无法解决写者饥饿及读写状态原子协调问题;需组合 std::atomic_flag 与 std::atomic reader count 并严格控制内存序。

为什么不能直接用 std::atomic 模拟读写锁
原子变量本身不提供“等待”或“条件阻塞”能力,而读写锁的核心语义之一是:当写者活跃时,新读者需等待;当读者活跃时,新写者需等待。单纯靠 std::atomic<int></int> 计数器(比如读者计数)无法安全处理“写者正在等待但读者持续涌入”的饥饿问题,也无法原子地协调读/写状态切换。常见错误是只用一个 std::atomic<int></int> 表示读者数 + 写者标志,结果在写者尝试获取锁时被新读者抢先递增,导致写者永远等不到读者数归零。
std::atomic_flag 搭配 reader count 的最小可行实现
可行方案是分离两个原子状态:一个 std::atomic_flag 专用于写者独占控制(test-and-set),一个 std::atomic<int></int> 统计当前活跃读者数。关键在于写者获取锁前必须先设置 flag,再等待读者数归零;读者则需先检查 flag 是否被设,再递增计数——且这两步必须有正确内存序保证可见性。
- 写者入口:
while (writer_flag.test_and_set(std::memory_order_acquire)) {},然后while (reader_count.load(std::memory_order_acquire) != 0) {} - 读者入口:先
if (writer_flag.test(std::memory_order_acquire)) return false;(快速失败),再reader_count.fetch_add(1, std::memory_order_relaxed) - 读者退出:仅
reader_count.fetch_sub(1, std::memory_order_relaxed),无需释放 barrier - 写者退出:
writer_flag.clear(std::memory_order_release)
注意:fetch_add 和 fetch_sub 用 relaxed 是安全的,因为 reader count 本身不用于同步,只供写者轮询;但 writer flag 的 acquire/release 是必须的,否则读者可能看到过期的 reader count 值。
实际使用中容易漏掉的三个细节
这个模式看似简单,但落地时极易出错:
- 读者路径里必须两次检查
writer_flag:一次在递增前(避免写者已上锁还继续进),一次在临界区入口(防止写者已在等待、但 reader_count 尚未归零) -
reader_count必须初始化为 0,且不能被写者修改——写者只读,读者只写,否则破坏线性一致性 - 如果要求写者优先(避免读者饿死写者),需额外引入一个
std::atomic<bool></bool>表示“写者已在排队”,并在读者检查时一并判断;否则读者可能无限涌入,让写者一直卡在 while 循环里
比起 std::shared_mutex,它到底省了什么又丢了什么
自建原子锁唯一优势是无系统调用、纯用户态、极低延迟(适合微秒级临界区);但它完全放弃公平性和唤醒机制——没有 waiter queue,写者靠忙等,高竞争下 CPU 利用率飙升。而 std::shared_mutex(C++17)底层依赖 futex 或类似机制,能真正挂起线程,但每次 lock/unlock 至少一次系统调用开销。如果你的读操作占比 >95%、临界区执行时间 std::shared_mutex 更稳妥,也更难写错。
真正难的是写者等待期间如何不被新读者插队——这需要精确的内存序组合和额外的状态位,不是加几个 atomic 就能解决的事。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











