
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_lock 和 std::unique_lock 都是 RaiI 类型,但它们的移动语义和可重用性常被误用。
立即学习“C++免费学习笔记(深入)”;
-
std::shared_lock支持移动、可多次lock()/unlock(),但不能在已锁定状态下再次lock()(会抛std::system_error) -
std::unique_lock同理,且支持延迟锁定(构造时不加锁,后续调用lock()) - 不要把锁对象返回或长期保存:锁的生命周期应严格绑定于临界区作用域;跨函数传递锁对象易引发死锁或提前释放
- 异常发生时 RAII 自动释放,这点安全;但若在临界区内抛异常且未捕获,需确保数据处于一致状态——锁解决并发,不解决逻辑一致性
实际用的时候,最麻烦的往往不是语法,而是读多写少场景下写线程被饿死,或者多个 shared_lock 在嵌套调用中意外延长了读锁持有时间。别只盯着“能编译”,得看运行时行为。