用GDB定位死锁的核心是直接观察谁在等锁、谁持有锁、锁地址是否一致;执行info threads识别阻塞线程,thread <n>和bt定位加锁点,p (pthread_mutex_t)<addr>查__owner字段确认持有者,最终交叉比对形成循环等待链。

死锁问题用 gdb 定位,核心就一条:别猜,直接看谁在等、谁在持、锁地址是否一致。只要符号完整、进程还活着,5 分钟内就能画出循环等待链。
info threads 一眼识别卡死线程
进入 gdb 后第一件事不是 bt,而是执行:
info threads
重点关注状态为 Blocked 或 Waiting 的线程,尤其末尾栈帧含以下任一关键词的:
-
__lll_lock_wait(glibc 底层阻塞点,最常见) pthread_mutex_lockstd::mutex::lock
如果多个线程停在同一个 pthread_mutex_lock 调用,且参数地址(如 %rdi 寄存器值)相同,基本可断定争抢同一把锁。此时不用急着切线程,先记下线程编号和锁地址。
thread & bt 定位具体加锁点
对每个可疑线程,依次执行:
thread <n><br>bt
关键不是看顶层,而是顺着栈往下找最近一次调用锁函数的位置,例如:
#3 0x00007f... in pthread_mutex_lock (mutex=0x7fffe40012a0) at ...
然后用 frame 3 跳过去,再 list 查源码上下文,确认它想锁的是哪个变量(比如 mtx_a)。必要时用:
print &mtx_a
对比其他线程打印出的地址,判断是否为同一把锁。注意:std::mutex 对象地址和内部 __data.__lock 地址不同,别混淆。
p mutex 查看持有者线程 ID
对已知锁地址(如 0x7fffe40012a0),在对应线程的栈帧中执行:
print *(pthread_mutex_t*)0x7fffe40012a0
关注输出中的 __owner 字段——这是当前持有该锁的 LWP(轻量级进程)ID。若为 0,说明未被占用;若为非零值(如 3786),就去 info threads 输出里找对应编号的线程,看它卡在哪、又在等哪把锁。
对于 std::mutex,调试符号足够时可尝试:
print ((std::mutex*)0x7fffe40012a0)->__lock
非零值通常即持有者 tid(glibc 实现依赖版本,不绝对可靠)。
thread apply all bt 快速扫全貌但慎信
命令:
thread apply all bt
能一次性打出所有线程栈,适合快速筛查。但它容易淹没关键信息——尤其是当线程数多、日志长时,__lll_lock_wait 可能被刷屏掩盖。更稳妥的做法是:
- 先
info threads锁定 2–3 个最可疑线程 - 再逐个
thread <n> && bt - 最后交叉比对锁地址与
__owner
避免陷入“所有线程都在等”的错觉——有些线程可能只是在 nanosleep 或 read,并非死锁参与者。
真正难的不是命令怎么输,而是看到两个 __owner 互相指向对方时,要立刻意识到:代码里加锁顺序没统一。这时候翻源码比继续 gdb 更有效——因为 gdb 只告诉你“谁卡在哪”,不告诉你“为什么这么写”。


















