C++中的std::shared_mutex怎么实现读写锁?(允许多读单写的并发控制)

14次阅读

c++kquote>std::shared_mutex是c++17引入的读写锁,需配合std::shared_lock(读)和std::unique_lock(写)使用,不可直接调用lock()/unlock(),且不兼容std::lock_guard。

C++中的std::shared_mutex怎么实现读写锁?(允许多读单写的并发控制)

std::shared_mutex 是 C++17 引入的读写锁,但不能直接构造使用

它本身不提供 lock() / unlock() 这类裸接口,必须配合 std::shared_lock(读锁)和 std::unique_lock(写锁)才能生效。常见错误是试图像 std::mutex 那样直接调用 mtx.lock() —— 这会编译失败,因为 std::shared_mutex 没有这些成员函数

  • std::shared_lock<:shared_mutex>:用于共享读访问,允许多个线程同时持有
  • std::unique_lock<:shared_mutex>:用于独占写访问,阻塞其他所有读/写操作
  • 注意:C++17 起才支持;C++14 及更早版本需用第三方库(如 Boost.Thread)或自实现

正确加读锁和写锁的写法示例

读操作必须用 std::shared_lock,写操作必须用 std::unique_lock(或 std::lock_guard 的写法仅限于独占语义,但 std::lock_guard 不支持 std::shared_mutex 的共享模式,所以只能用 std::unique_lock)。

#include  #include  #include   std::shared_mutex rw_mtx; int data = 0;  void reader() {     std::shared_lock lock(rw_mtx); // ✅ 正确:共享读锁     // ... 读取 data }  void writer() {     std::unique_lock lock(rw_mtx); // ✅ 正确:独占写锁     ++data; }
  • 不能用 std::lock_guard<:shared_mutex>:编译报错,该模板不接受 std::shared_mutex
  • 不能对同一 std::shared_mutex 同时持有一个 std::shared_lock 和一个 std::unique_lock:写锁会阻塞新读锁获取,已持有的读锁也会被写锁等待者阻塞直到全部释放
  • 推荐显式指定模板参数,避免依赖 CTAD(C++17 起支持,但某些旧编译器或严格模式下可能推导失败)

性能与兼容性要注意的几个现实问题

不同标准库实现对 std::shared_mutex 的底层策略差异很大,直接影响吞吐和公平性。

  • libstdc++(GCC):在 linux 上通常基于 futex 实现,但早期版本(如 GCC 7~9)的 std::shared_mutex 性能较差,存在写饥饿风险(大量读锁导致写锁长期无法获取)
  • libc++(Clang / macOS):实现更成熟,读写锁切换开销较低,但 macos 10.15+ 才完整支持 C++17 线程设施
  • windows MSVC:从 VS 2015 Update 3 起支持,底层映射到 SRWLock,性能较好,但要注意调试模式下锁竞争检测可能误报
  • 若需跨平台且高负载,建议压测实测;低延迟场景慎用,因共享锁的 acquire/release 开销仍高于普通 mutex

容易忽略的生命周期和异常安全细节

std::shared_lockstd::unique_lock 都是 RaiI 类型,但它们的移动语义和可重用性常被误用。

立即学习C++免费学习笔记(深入)”;

  • std::shared_lock 支持移动、可多次 lock()/unlock(),但不能在已锁定状态下再次 lock()(会抛 std::system_error
  • std::unique_lock 同理,且支持延迟锁定(构造时不加锁,后续调用 lock()
  • 不要把锁对象返回或长期保存:锁的生命周期应严格绑定于临界区作用域;跨函数传递锁对象易引发死锁或提前释放
  • 异常发生时 RAII 自动释放,这点安全;但若在临界区内抛异常且未捕获,需确保数据处于一致状态——锁解决并发,不解决逻辑一致性

实际用的时候,最麻烦的往往不是语法,而是读多写少场景下写线程被饿死,或者多个 shared_lock 在嵌套调用中意外延长了读锁持有时间。别只盯着“能编译”,得看运行时行为。

text=ZqhQzanResources