linux 死锁怎么排查/定位(gdb 实战)
答案:用 gdb -p <PID> 附加卡住的进程,thread apply all bt 看所有线程栈——多个线程各持一把锁又互等对方,栈上能看到阻塞在锁函数,形成循环等待。Java 用 jstack。
一、必要条件与典型形态
死锁四要素:互斥、持有并等待、不可剥夺、循环等待。
text
线程 A: 持 锁1 → 等待 锁2
线程 B: 持 锁2 → 等待 锁1
→ 互相等,谁也无法推进 → 死锁二、gdb 定位流程
bash
# 1. 附加到卡住的进程
gdb -p <PID>
# 2. 打印所有线程栈
(gdb) thread apply all bt
# 3. 观察:多个线程都阻塞在锁函数,互相等待
# 线程1: pthread_mutex_lock (锁对象 X) ← 等待 X
# 线程2: pthread_mutex_lock (锁对象 Y) ← 等待 Y
# 结合栈上各自持锁的调用,确认环形依赖三、不同环境工具
| 环境 | 工具 | 怎么看 |
|---|---|---|
| C/C++ | gdb -p PID + thread apply all bt | 栈在 pthread_mutex_lock/futex |
| Java | jstack -l <PID> | "Found one Java-level deadlock" |
| 通用 | pstack <PID> | 打印线程栈快照 |
| 系统调用 | strace -p PID | 卡在 futex(FUTEX_WAIT) |
四、死锁 vs 活锁 vs 饿死
| 现象 | 特征 | 栈表现 |
|---|---|---|
| 死锁 | 互相等锁,无进展 | 阻塞在锁函数 |
| 活锁 | 互相让锁,无进展 | 在抢占/重试循环 |
| 饿死 | 某线程一直拿不到锁 | 其他线程反复拿锁 |
五、预防措施
- 统一加锁顺序:所有线程按相同顺序拿锁,消除循环等待;
- 避免持锁调外部:持锁时不做回调/网络/重操作;
- 超时锁:
pthread_mutex_timedlock、trylock 让死锁"变活"; - 锁粒度:能小则小,减少互相覆盖的区间。
六、常见坑
- 容器内 gdb attach 失败:需要
--cap-add=SYS_PTRACE或在宿主机查; - 没符号看不到函数名:用
-g编译,或pstack看符号; strace只看到 futex 不够:futex 等待是正常调度,要结合栈确认是"互等"还是"等待工作";- 死锁不一定是互斥锁:读写锁、条件变量错误使用也会造成阻塞。
深度入口
- gdb 死锁定位实战(含示例):gdb 定位死锁
- C++ 调试工具集:C++ 调试指南
- 系统调用观察:strace
- 崩溃类问题排查:core dump 分析
FAQ
Q: linux 死锁怎么排查? A: gdb -p PID + thread apply all bt 看所有线程栈,找互相等锁的环形等待;Java 用 jstack。
Q: 死锁的必要条件? A: 互斥、持有并等待、不可剥夺、循环等待。典型:A 持 1 等 2、B 持 2 等 1。
Q: 怎么用 gdb 定位死锁? A: gdb -p PID → thread apply all bt → 找都阻塞在 pthread_mutex_lock 且互相等待的线程。
Q: 死锁和活锁、饿死区别? A: 死锁=互相等锁;活锁=互相让锁;饿死=某线程一直拿不到锁。
Q: 怎么预防死锁? A: 统一加锁顺序、避免持锁回调、超时锁(trylock/timedlock)、锁粒度小。
Q: 容器/Java 环境怎么查死锁? A: Java jstack -l 直接报;容器 gdb 需 SYS_PTRACE 或在宿主机。
一句话总结:死锁排查 = gdb -p PID + thread apply all bt 找"各持一锁互相等"的环形依赖(Java 用 jstack),预防靠统一加锁顺序、避免持锁回调、超时锁;strace 卡在 futex 只是线索,要看栈确认互等。