死锁需通过gdb观察线程阻塞状态并交叉推断资源持有关系来确认:先用info threads识别卡在pthread_mutex_lock等调用的线程,再用thread与bt定位加锁点及锁地址,最后比对各线程持锁与等待关系,人工拼出循环等待链。

死锁不是靠“检测条件”发现的,而是通过观察线程阻塞状态+交叉推断资源持有关系来确认。gdb 本身不提供 check_deadlock 这类命令,它只暴露现场——你得自己拼出循环等待链。
看线程状态:哪些线程卡在锁上
附着后第一件事是执行 info threads。重点识别以下三类状态:
- 线程名或栈顶含
pthread_mutex_lock、std::mutex::lock、__lll_lock_wait的,大概率正在等锁 - 多个线程停在同一地址的
pthread_mutex_lock调用点(可用bt full查%rdi寄存器值)→ 它们争抢同一把锁 - 线程状态显示
Blocked或长时间无输出,但 CPU 占用极低 → 符合死锁典型现象
查调用栈:定位具体锁变量和加锁点
对每个疑似阻塞线程,切换过去并回溯:
- 执行
thread N切换,再bt查看完整栈;不要只看顶层,要找到最靠近顶部的std::mutex::lock或pthread_mutex_lock调用行 - 用
frame M跳到那一层,再list看源码上下文,确认它试图 lock 哪个变量(比如mtx_a) - 若变量名被优化掉,可用
print &mtx_a获取地址,再与其他线程中print &mtx_b对比是否一致 - 对
std::mutex,可尝试print ((std::mutex*)0x7fffe40012a0)->__lock(需调试符号),非零值通常表示已被某线程持有
交叉验证:谁拿了锁、谁在等谁
Linux 下 pthread mutex 不记录持有者,但可通过行为间接判断:
- 某个线程刚执行完
pthread_mutex_unlock或正处在临界区出口附近 → 很可能是该锁的当前或上一任持有者 - 线程 A 卡在
mtx_a.lock(),线程 B 卡在mtx_b.lock(),而线程 C 正在执行mtx_a.unlock()→ C 很可能刚释放mtx_a,但还没来得及拿mtx_b - 若线程 A 已进入
mtx_a保护的代码段、且尚未调用mtx_b.lock(),而线程 B 已拿到mtx_b并正调用mtx_a.lock()→ 典型 A 持 A 等 B、B 持 B 等 A
编译与运行时辅助:让 gdb 看得更清楚
别让优化和缺失符号干扰判断:
- 编译必须带
-g -O0:避免内联导致栈帧丢失,确保bt能看到你写的lock_guard或mtx.lock()行 - 运行前执行
ulimit -c unlimited:万一死锁触发崩溃(如超时 abort),能留下core文件供事后分析 - 启用 ThreadSanitizer:编译加
-fsanitize=thread -g,它能在运行时直接报lock-order-inversion,比 gdb 更早发现问题路径 - 避免用局部对象构造锁名:全局命名的
std::mutex g_user_map_mu比函数内static std::mutex tmp更易在 gdb 中识别和追踪
真正难的不是看懂单个线程的栈,而是把三四个线程的锁获取/释放动作在时间线上串起来,还原出那个“谁先拿了什么、又等什么”的闭环。这个过程没法自动化,必须人工比对地址、变量名、调用顺序——这也是为什么很多团队会在设计阶段就强制约定锁等级,而不是等 gdb 里拼图。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











