必须用raii智能锁管理mutex,禁用手动lock/unlock;多锁必用std::lock避免顺序依赖;慎用recursive_mutex,优先重构临界区;死锁定位用timed_mutex加超时日志。

std::mutex 加锁后必须配对解锁,否则必然死锁
死锁最常见原因不是逻辑复杂,而是 std::mutex 的生命周期和作用域没管住。比如在函数中途 return、抛异常、或提前 break,导致 mtx.unlock() 没执行到。
- 永远用
std::lock_guard或std::unique_lock管理锁,别手写lock()/unlock() -
std::lock_guard最简够用:构造即加锁,析构即解锁,RAII 保底 - 需要延迟加锁、手动释放、或转移所有权时,才用
std::unique_lock - 别在多个
std::mutex上裸调lock()—— 顺序不一致就容易循环等待
多 mutex 同时加锁要用 std::lock 避免顺序依赖
两个线程分别按不同顺序对 mtx_a 和 mtx_b 调用 lock(),50% 概率卡死。这不是概率问题,是确定性死锁。
- 正确做法:用
std::lock(mtx_a, mtx_b)一次性尝试获取所有锁(内部用无死锁算法) - 配合
std::adopt_lock构造std::lock_guard:std::lock_guard<:mutex> g1(mtx_a, std::defer_lock); std::lock_guard<:mutex> g2(mtx_b, std::defer_lock); std::lock(mtx_a, mtx_b);</:mutex></:mutex> - 别用嵌套
lock_guard套娃,也别靠“我保证线程 A 总先锁 a 再锁 b”——这种假设在线程调度面前毫无意义
std::recursive_mutex 不解决设计问题,只掩盖错误
有人遇到“同一个线程重复 lock 就崩”,第一反应是换 std::recursive_mutex。但它不修复根本问题:为什么同一逻辑要多次进临界区?这往往是锁粒度太粗或函数职责混乱的信号。
-
std::recursive_mutex允许同一线程重复加锁,但性能差、调试难、且无法防止跨线程死锁 - 优先检查是否能拆分临界区,或把共享数据封装成线程局部副本
- 仅当确实需要递归调用(如解析嵌套表达式、事件回调重入)且无法重构时,才考虑它
- 注意:
std::lock_guard不支持std::recursive_mutex的自动释放计数,得用std::unique_lock
死锁现场很难复现?加 timeout 或用 std::timed_mutex 定位
生产环境死锁往往偶发、难抓栈,等 gdb 连上去早凉了。与其猜,不如让锁自己“喊话”。
立即学习“C++免费学习笔记(深入)”;
- 用
std::timed_mutex替代std::mutex,在try_lock_for()失败时打日志 + abort 或 panic - 示例:
if (!mtx.try_lock_for(200ms)) { log("failed to lock after 200ms"); std::abort(); } - 开发期可加全局钩子:重载
std::mutex::lock()(不行,不可重载),所以更实际的是封装一层带计时/堆栈记录的SafeMutex - 注意:Windows 下
std::timed_mutex在旧 MSVC 版本可能降级为普通 mutex,需验证
真正麻烦的不是锁怎么写,而是哪些变量该被锁、锁的范围是否覆盖了所有读写路径、以及有没有人悄悄绕过锁直接访问共享内存 —— 这些没法靠语法检查,只能靠代码审查和 ASan/TSan 配合。








