死锁发生时程序卡住,gdb是最直接排查入口:用gdb -p <pid>附加进程,info threads查看线程状态,thread <n>+bt定位是否卡在mutex或condition_variable等待;多线程停在不同mutex lock且互持对方所需锁即为环形等待。

死锁发生时程序卡住,gdb 是最直接的排查入口
当 C++ 多线程程序突然不响应、CPU 降为 0、但进程还活着,大概率是死锁。此时别急着改代码,先用 gdb 附加进程看线程在等什么:
- 运行
gdb -p <pid>,进 gdb 后执行info threads查看所有线程状态 - 对每个线程执行
thread <n>+bt,重点看是否卡在pthread_mutex_lock、std::mutex::lock()或std::condition_variable::wait() - 若多个线程都停在不同 mutex 的 lock 调用上,且各自已持有一个对方需要的锁——这就是典型环形等待
std::mutex 没有内置超时,必须手动加 std::timed_mutex 或封装防护
原生 std::mutex 一旦死锁,程序永远卡住,无法自动发现。想让死锁“暴露出来”,得主动引入可检测机制:
- 把高风险的嵌套锁区域换成
std::timed_mutex,用try_lock_for(100ms)替代lock() - 失败时记录日志并 abort 或抛异常,例如:
if (!mtx.try_lock_for(std::chrono::milliseconds(200))) { log_deadlock_risk(); std::abort(); } - 注意:不能只在一处加超时,要覆盖所有可能形成锁序环的路径,否则漏掉一环就白加
用 std::lock 和 std::scoped_lock 避免手写锁序,从源头减少死锁概率
手动按顺序调用 mtx1.lock(); mtx2.lock(); 极易出错——两个线程以相反顺序加锁就会死锁。C++17 提供了更安全的方案:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
-
std::scoped_lock<std::mutex, std::mutex> guard(mtx1, mtx2);自动按地址升序加锁,保证全局一致顺序 -
std::lock(mtx1, mtx2);也是原子性加锁,配合std::lock_guard分别构造(适合需延迟构造 guard 的场景) - 它们不能解决“业务逻辑本身要求 A→B 和 B→A 两种路径”的问题,但能消灭因粗心导致的锁序不一致
静态分析工具如 ThreadSanitizer 可在运行时捕获锁序冲突
编译期无法发现死锁,但 ThreadSanitizer(TSan)能在程序运行中检测到潜在的锁序反转(lock-order-inversion),这是死锁的前兆:
立即学习“C++免费学习笔记(深入)”;
- 用
clang++ -fsanitize=thread -g或g++ -fsanitize=thread -g编译(注意:TSan 对 GCC 支持有限,推荐 Clang) - 运行时若出现类似
WARNING: ThreadSanitizer: lock-order-inversion的输出,说明两个线程以不同顺序获取了同一组 mutex - 它不保证 100% 触发死锁,但只要复现一次锁序反转,就说明存在死锁风险,必须重构锁粒度或访问路径
真正难排查的不是“哪里卡住了”,而是“为什么这里会形成环”。很多死锁藏在间接调用链里——比如 A 函数锁 X 后调用插件回调,插件又去锁 Y;而另一个线程正相反。这种跨模块、跨抽象边界的锁依赖,光靠堆栈看不出全貌,得结合锁持有图(lock graph)手工梳理。

















