C++ 死锁调试全流程实操手册:从复现到 GDB 定位
一、背景
AB-BA 死锁是多线程工程里最经典的 bug。
两个线程各持一把锁,互相等对方释放,程序永久卡死。
本文按 SOP 整理完整排查流程,从修改代码、编译运行、附加 GDB 到定位现场,直接照着操作即可。
二、SOP 实操
| 阶段 | 核心操作 | 说明 |
|---|---|---|
| 一 | 修改 exercise6_debug.cpp |
替换 main 为复现死锁的调用 |
| 二 | 编译并运行 | 制造死锁现象 |
| 三 | 附加 GDB | 解除 ptrace 限制后 attach |
| 四 | 定位现场 | thread apply all bt 查看堆栈 |
| 五 | 干净收尾 | 退出 GDB,强制结束进程 |
三、第一阶段:修改源代码
在 VS Code 左侧文件列表双击打开 exercise6_debug.cpp。
删除文件最下方的 int main() 函数,替换为以下代码:
cpp
int main() {
std::cout << "=== 复现AB-BA死锁 ===" << std::endl;
std::thread t1(threadA_Bad);
std::thread t2(threadB_Bad);
t1.join();
t2.join();
return 0;
}
按 Ctrl + S 保存文件。
四、第二阶段:编译与运行
打开 VS Code 底部终端,输入编译命令:
bash
g++ -g -O0 -std=c++17 exercise6_debug.cpp -o exercise6_debug
编译完成后运行:
bash
./exercise6_debug
现象 :终端输出 === 复现AB-BA死锁 ===,然后程序彻底卡死不动。
注意:不要关闭这个终端,后面要在这里强制结束进程。
五、第三阶段:附加 GDB 调试器
点击 VS Code 底部终端栏右上角的 + 号,打开一个全新终端窗口。
第一步:查找进程号 PID
bash
ps aux | grep exercise6_debug
在输出结果中找到 ./exercise6_debug 左侧的数字,例如 7782,这就是 PID。
第二步:解除系统调试限制
bash
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
输入系统密码后回车。这一步让 GDB 有权附加到卡死进程上。
第三步:附加 GDB
把 <PID> 换成刚才查到的真实数字:
bash
gdb -p <PID>
现象 :GDB 自动暂停死锁程序,进入 (gdb) 调试交互界面。
六、第四阶段:定位死锁现场
在 (gdb) 提示符后输入:
thread apply all bt
屏幕会打印所有线程的调用堆栈。重点关注包含 exercise6_debug.cpp 的行:
- 某个线程 (如 Thread 3):卡在
exercise6_debug.cpp第 12 行,持有锁 A,死等锁 B。 - 另一个线程 (如 Thread 2):卡在
exercise6_debug.cpp第 19 行,持有锁 B,死等锁 A。
这就是 AB-BA 死锁的完整铁证。
七、第五阶段:干净收尾
在 (gdb) 提示符后输入 q 并回车,退出 GDB 调试环境。
切换回第二步运行程序的终端窗口,按 Ctrl + C 强制结束卡死的进程。
至此,一套标准的死锁复现、GDB 实战排查全流程已经完美通关。
八、工程要点
- 编译必加
-g -O0:-g保留调试符号,-O0关闭优化,否则堆栈行号对不上。 - ptrace_scope 限制 :Linux 默认禁止非子进程附加,需要临时解除,调试完可恢复为
1。 - 死锁特征:多个线程各自持有一把锁,同时等待另一把锁,形成循环依赖。
- 根治方案 :多锁场景统一用
std::scoped_lock一次获取,从根源杜绝 AB-BA 顺序问题。