thread apply all bt是快速定位多线程阻塞点的核心命令,逐线程打印调用栈,重点分析栈顶系统调用(如pthread_mutex_lock)及其上层业务代码,结合bt full、print锁地址与__m_owner字段比对,可精准识别死锁或争锁。

thread apply all bt 看所有线程的阻塞点
直接执行 thread apply all bt 是最快确认哪些线程卡在哪的命令。它会逐个打印每个线程的完整调用栈,重点看栈顶(最上面一行)函数:如果停在 pthread_mutex_lock、__lll_lock_wait、sem_wait、read、write 或 nanosleep 这类系统调用上,基本就是阻塞点了。
常见误区是只扫一眼顶层函数就下结论。比如看到 pthread_mutex_lock,得继续往下看它的调用者——真正的问题往往出在上层业务代码里那行 mtx1.lock() 或 pthread_mutex_lock(&g_config_mutex),而不是 libc 的实现内部。
- 用
t a a bt(thread apply all bt的缩写)可快速输出,适合终端窄时阅读 - 若线程太多刷屏,先
set logging on把结果导出到文件再分析 - 注意区分“真阻塞”和“假等待”:停在
nanosleep可能只是定时任务休眠,不一定是死锁;但多个线程同时停在同一个pthread_mutex_lock且地址一致,大概率是争同一把锁
怎么确认是同一把锁被争抢
当发现多个线程都卡在 pthread_mutex_lock,下一步必须验证它们等的是不是同一把锁。关键看参数地址是否一致——这个地址通常出现在调用栈第二或第三层的寄存器里(x86_64 下常为 %rdi),或通过 bt full 查看传参值。
操作步骤:
- 用
thread <n></n>切到某个阻塞线程,再bt full查看完整栈和局部变量 - 找到
pthread_mutex_lock上一层调用,用frame <n></n>跳进去,再list看源码上下文,确认锁变量名(如&db_mutex) - 用
print &db_mutex打印地址,或直接print *(pthread_mutex_t*)0x7fffe40012a0(把地址换成实际值)查看锁结构体内容 - 对比其他线程中该锁的
__m_owner字段:若为 0 表示未被持有;若非零(如0x2527),再用print 0x2527转成十进制,去info threads里找对应 LWP ID 的线程
info threads 和当前线程状态怎么看
info threads 输出里带 * 的是当前调试线程,每行末尾的 (LWP XXXX) 是 Linux 线程 PID,这个 ID 和 /proc/<pid>/task/</pid> 下目录名一致,可用于交叉验证。
注意:info threads 不显示线程是否阻塞,只列存在状态;真正判断是否“活”着,得结合 bt 结果——如果某线程的栈顶是 clone 或 start_thread 且没后续调用,可能是刚创建还没跑起来;如果栈顶是 wait 或 epoll_wait,说明在等事件,未必是问题。
- 新线程启动时,GDB 会自动打印类似
[New Thread 1073951360 (LWP 12900)]的提示,可据此捕获线程诞生瞬间 - 用
thread <n></n>切换后,where或bt才能反映该线程真实执行位置 - 别依赖
ps -T -p <pid></pid>的状态列(如Sl),GDB 内部线程状态更准
为什么 gdb 7.0 以下版本不适合查死锁
旧版 GDB(如 6.3)对多线程支持极弱:thread apply all 命令不存在,info threads 输出不全,切换线程后 bt 经常报错或显示错误栈。最关键的是,它无法正确读取现代 glibc 中 pthread mutex 的内部字段(如 __m_owner),导致你根本没法查谁持有了锁。
检查方法很简单:gdb --version,确保 ≥ 7.0。生产环境建议用 8.2+ 或 9.x,它们对 C++11 std::mutex 的符号解析也更稳定。
- 编译程序时务必加
-g,否则bt full看不到变量名和源码行号 - 如果
bt里大量出现??,八成是没调试符号,或者用了 strip - 某些发行版自带的 gdb 可能被精简过,可用
gdb -nx -q -ex 'help thread'测试是否支持多线程命令











