c++oding="utf-8" ?>
用gdb -p 附加到运行中死锁进程,执行info threads查看线程状态,thread apply all bt full 50打印所有线程完整调用栈,结合pthread_mutex_lock调用位置及参数地址,人工比对各线程锁持有与等待关系以定位死锁环。

怎么用 GDB 附加到正在死锁的多线程进程
直接启动 GDB 调试死锁程序往往来不及——线程已经卡住,但进程还在运行。更可靠的做法是让程序先跑起来,再用 gdb -p <pid></pid> 附加。确保编译时加了 -g(保留调试符号),且没开 -O2 以上优化(否则变量名和栈帧可能被优化掉,backtrace 会不完整)。
常见错误:用 ps aux | grep your_app 找错 PID,比如匹配到 shell 进程或子进程;建议改用 pgrep -f "your_binary_name" 或在程序启动时打印 getpid() 到日志。
附加后,GDB 默认停在任意一个线程上,此时不能直接假设主线程就是卡点——得主动检查所有线程状态:
-
info threads——列出所有线程 ID 和当前状态(running/stopped/idle) - 注意看哪些线程状态是
Blocked或长时间停在pthread_mutex_lock、__lll_lock_wait等系统调用里
怎么一次性打印所有线程的完整调用栈
thread apply all bt 是核心命令,但它默认只显示前 10 层栈帧,而死锁常发生在深层调用(比如 RAII 锁 + 模板展开后),容易漏关键帧。必须加 full 和指定深度:
-
thread apply all bt full 50——对所有线程打印最多 50 层完整栈帧(含局部变量) - 如果某线程栈太深或损坏,GDB 可能报
Cannot access memory at address ...,这时换用bt 20(不带full)避免卡住 - 想单独看某个线程(比如线程 3):先
thread 3切换,再bt full
注意:bt full 在某些旧版 GDB(如 7.12 以下)可能崩溃,可降级为 bt + info registers + x/10i $pc 辅助定位。
怎么识别哪个线程持有了哪个 mutex 并导致死锁
GDB 本身不解析 pthread mutex 内部状态,但可通过符号和上下文交叉推断。关键看三类信息:
- 线程栈中是否出现
pthread_mutex_lock、__GI___pthread_mutex_lock或 C++ 标准库中的std::mutex::lock(),并观察其参数地址(如rdi或第一个栈参数) - 用
info proc mappings确认该地址是否落在 libc 或 libstdc++ 的数据段内(排除野指针) - 对疑似持有锁的线程,执行
frame 2(跳到锁调用上层),再print *(pthread_mutex_t*)0xADDR(需知道 mutex 地址)——但注意:多数 mutex 是静态或局部变量,地址需从上层函数变量中提取,比如print lock_(若变量名是lock_)
更实用的方法是配合源码:在 bt 输出中找到各线程阻塞在哪个源文件哪一行,然后人工比对加锁顺序。典型死锁模式是线程 A 持有 mu1 等 mu2,线程 B 持有 mu2 等 mu1。
为什么有时候 thread apply all bt 显示所有线程都在 nanosleep 或 futex?
这不是假象,而是 Linux 下 pthread mutex 的底层实现机制:当锁争用时,glibc 会先自旋,失败后调用 futex(FUTEX_WAIT) 进入内核等待,GDB 抓到的就是这个系统调用帧。此时线程实际卡在用户态锁上,只是栈顶显示为内核入口。
真正要关注的是 futex 调用的上一层——通常是 pthread_mutex_lock 或 std::mutex::lock,再往上翻 2–3 帧,基本能看到你代码里的函数名和行号。如果上层全是 ??,说明缺少调试符号或二进制被 strip 过。
另外,某些场景(如用了 std::shared_mutex 或第三方锁)可能走不同路径,栈中会出现 __shared_mutex_base::lock_shared 等,需按具体类型查文档。
死锁分析最难的不是看栈,而是把多个线程的锁持有/等待关系串成环——这一步没法靠 GDB 自动完成,必须人工对照源码和 bt 输出逐行画依赖图。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











