linux 死锁问题查找方法

本文介绍了一种通过pstack和gdb工具定位Linux系统中进程死锁的方法。具体步骤包括使用pstack获取进程堆栈信息,利用gdb进一步分析锁定状态,最终确定导致死锁的线程及原因。

linux 死锁问题查找方法

步骤

pstack 输出进程堆栈信息到文件,查找死锁线程
pstack  进程pid  >  pstack.txt

在这里插入图片描述
__lll_lock_wait () 可能就是死锁相关的线程,记录LWP 31391

gdb 查看进程的堆栈信息

#进入正在运行的进程
gdb attach 进程pid
#查看线程信息,查找到 LWP 31391 对应的线程ID
(gdb) info threads
在这里插入图片描述

#进入线程
(gdb) t 线程ID
#查看线程堆栈
(gdb) bt
在这里插入图片描述
#查看第7帧
(gdb) f 7
在这里插入图片描述
__lock = 2,m_distCs 为死锁对象, 查找锁的拥有者线程(__owner的值) LWP 31387

#查看线程信息,查找到 LWP 31387对应的线程ID
(gdb) info threads
在这里插入图片描述

(gdb) t 线程ID
#查看线程堆栈 分析死锁原因
(gdb) bt
在这里插入图片描述
查看堆栈代码 DistManager::closeDistServer 函数中有锁对象 m_distCs ;
pthread_join () 查看代码 进行了停止线程并等待线程退出,被停止的线程一直不退出,导致这个线程一直未释放锁对象 m_distCs ,这就是死锁的原因;
那么就根据代码逻辑去找到对应的线程处理函数;
我的代码线程处理函数是 GBTcpMediaAgent::runpstack.txt 搜索这个函数, 查找到对应线程是 LWP 31394
在这里插入图片描述
查看代码在DistManager::getDistServer函数中正在等待获取锁对象锁对象 m_distCs。

总结原因:
线程   LWP 31387  持有锁对象m_distCs, 等待线程  LWP 31394 结束,
线程  LWP 31394  再等待获取锁对象m_distCs,导致死锁。
<think>我们正在处理一个关于在Linux下调试多线程死锁问题。用户希望了解如何定位和调试多线程死锁问题。根据提供的引用,我们可以总结出以下步骤: 1. **死锁概念**:死锁是指两个或更多线程相互等待对方释放资源,导致所有线程都无法继续执行的状态。死锁的四个必要条件是:互斥、请求与保持、不可剥夺和循环等待[^1][^2]。 2. **调试步骤**: - **步骤1:找到死锁进程的PID** 使用`ps -ef | grep 程序名`命令找到出现死锁的进程ID。 - **步骤2:附加GDB到进程** 使用`gdb -p PID`命令附加到目标进程。 - **步骤3:查看所有线程信息** 在GDB中使用`info threads`命令,查看所有线程的状态。通常,死锁的线程会显示为`lock_wait`(等待锁)状态。 - **步骤4:切换到等待锁的线程** 使用`thread 线程号`切换到等待锁的线程(例如,引用[1]中的例子是线程6)。 - **步骤5:查看线程的堆栈** 使用`bt`(backtrace)命令查看该线程的堆栈,找出线程在哪个函数中等待锁。 - **步骤6:检查锁的状态** 在堆栈中找到锁的变量(例如,mutex),然后使用`print`或`x`命令查看锁的状态。例如,引用[1]中提到可以使用`x/3d 锁地址`来查看锁的内部状态(三个值:`__lock`, `__count`, `__owner`),其中`__owner`表示当前持有该锁的线程ID(注意,这个线程ID是系统线程ID,即LWP)。 - **步骤7:切换到持有锁的线程** 根据上一步得到的线程ID(LWP),使用`info threads`找到对应的线程号(在GDB中显示的线程编号),然后使用`thread 线程号`切换到该线程。 - **步骤8:查看持有锁线程的堆栈** 使用`bt`查看该线程的堆栈,分析它为什么没有释放锁,以及它是否在等待其他锁(可能形成循环等待)。 3. **使用VSCode调试死锁(可选)** 引用[3]提供了一个使用VSCode调试死锁的示例,但这里我们主要使用GDB命令行。 4. **GDB常用命令**(引用[4]): - `info threads`:列出所有线程。 - `thread tid`:切换到线程tid。 - `break [位置] thread [tid] [条件]`:在指定线程的指定位置设置断点。 - `thread apply all bt`:打印所有线程的堆栈(类似于`pstack`命令)。 **示例调试过程(基于引用[3]的代码)**: 假设我们有如下死锁代码(两个线程,两个互斥锁,形成循环等待): ```cpp #include <iostream> #include <mutex> #include <thread> #include <chrono> std::mutex m1; std::mutex m2; void thread_func1() { std::lock_guard<std::mutex> guard1(m1); std::this_thread::sleep_for(std::chrono::seconds(1)); std::lock_guard<std::mutex> guard2(m2); // 等待m2 } void thread_func2() { std::lock_guard<std::mutex> guard2(m2); std::this_thread::sleep_for(std::chrono::seconds(1)); std::lock_guard<std::mutex> guard1(m1); // 等待m1 } int main() { std::thread t1(thread_func1); std::thread t2(thread_func2); t1.join(); t2.join(); return 0; } ``` **调试步骤**: 1. 编译程序(使用g++并带上`-g`选项): ```bash g++ -g deadlock.cpp -o deadlock -lpthread ``` 2. 运行程序,程序会死锁(卡住)。 3. 打开另一个终端,使用`ps -ef | grep deadlock`找到进程ID。 4. 使用GDB附加到进程: ```bash gdb -p <PID> ``` 5. 在GDB中执行: ```gdb (gdb) info threads Id Target Id Frame 2 Thread 0x7ffff6e2c700 (LWP 12345) "deadlock" __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135 1 Thread 0x7ffff7fe9740 (LWP 12344) "deadlock" __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135 ``` 这里我们看到两个线程都在`__lll_lock_wait`(等待锁)。 6. 分别切换到两个线程查看堆栈: ```gdb (gdb) thread 2 (gdb) bt #0 __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135 #1 0x00007ffff7e7c4a2 in __GI___pthread_mutex_lock (mutex=0x5555555580a0 <m1>) at ../nptl/pthread_mutex_lock.c:115 #2 0x000055555555531e in __gthread_mutex_lock (__mutex=0x5555555580a0 <m1>) at /usr/include/x86_64-linux-gnu/c++/9/bits/gthr-default.h:749 #3 0x00005555555553a7 in std::mutex::lock (this=0x5555555580a0 <m1>) at /usr/include/c++/9/bits/std_mutex.h:100 #4 0x00005555555555a2 in std::lock_guard<std::mutex>::lock_guard (this=0x7ffff6e2beb0, __m=...) at /usr/include/c++/9/bits/std_mutex.h:197 #5 0x00005555555552a7 in thread_func2 () at deadlock.cpp:18 #6 0x00007ffff7e7b6df in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6 #7 0x00007ffff7e7aef2 in std::execute_native_thread_routine (__p=0x555555557d70) at ../../../../../libstdc++-v3/src/c++11/thread.cc:80 #8 0x00007ffff7df7609 in start_thread (arg=<optimized out>) at pthread_create.c:477 #9 0x00007ffff7d1a293 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95 (gdb) thread 1 (gdb) bt ... 类似地,可以看到线程1在等待m2 ... ``` 7. 通过堆栈,我们可以确定每个线程在等待哪个锁。例如,线程2(thread 2)在尝试获取`m1`(在`deadlock.cpp`的第18行),而线程1在尝试获取`m2`。 8. 我们可以进一步检查锁的所有者。例如,在线程2的堆栈中,我们看到它卡在获取`m1`,那么我们可以查看`m1`的当前所有者: ```gdb (gdb) print m1 $1 = {__data = {__lock = 2, __count = 0, __owner = 12344, ...}, ...} ``` 这里`__owner`是12344(LWP),然后我们通过`info threads`找到LWP为12344的线程(即线程1)。同样,在线程1中,它可能持有`m2`而等待`m1`,这样我们就找到了循环等待。 9. 结合代码,我们可以分析出死锁的原因:线程1持有`m1`并等待`m2`,线程2持有`m2`并等待`m1`。 **避免死锁的建议**: - 按固定顺序获取锁(例如,总是先获取m1再获取m2)。 - 使用`std::lock`一次性获取多个锁(避免死锁)。 **总结**: 在Linux下使用GDB调试死锁的关键步骤是:附加到进程,查看所有线程状态,切换到等待锁的线程,查看堆栈和锁的状态,定位持有锁的线程,分析循环等待链。 §§相关问题§§ 1. 如何避免多线程编程中的死锁? 2. 除了GDB,还有哪些工具可以检测死锁? 3. 在C++中,如何使用RAII(如`std::lock_guard`)来管理锁?</think>### Linux 下定位和调试多线程死锁问题指南 死锁是多线程编程中的常见问题,当多个线程因竞争资源而相互等待时发生。以下是完整的定位和调试流程: #### 一、死锁特征识别 1. **必要条件**(满足以下全部条件时发生死锁): - 互斥条件:资源每次只能被一个线程使用 - 请求与保持:线程持有资源时请求新资源 - 不可剥夺:资源只能由持有线程释放 - 循环等待:线程间形成资源等待环[^1][^2] 2. **现象判断**: - 程序无响应但未崩溃 - CPU 使用率异常低 - 日志输出停滞在特定位置 #### 二、GDB 调试死锁步骤 ##### 1. 附加到目标进程 ```bash # 查找进程ID ps -ef | grep 你的程序名 # 附加GDB gdb -p <PID> ``` ##### 2. 查看线程状态 ```gdb (gdb) info threads Id Target Id Frame 1 Thread 0x7f... __lll_lock_wait() # 等待锁的线程 2 Thread 0x7f... thread_func1() 3 Thread 0x7f... thread_func2() ``` ##### 3. 分析等待锁的线程 ```gdb (gdb) thread 1 # 切换到等待锁的线程 (gdb) bt # 查看调用栈 #0 __lll_lock_wait () at lowlevellock.c:135 #1 0x00007f... in __GI___pthread_mutex_lock (mutex=0x6010a0) at pthread_mutex_lock.c:115 #2 0x000055... in std::lock_guard<std::mutex>::lock_guard (..., __m=...) #3 0x000055... in thread_func1 () at example.cpp:15 ``` ##### 4. 检查互斥锁状态 ```gdb (gdb) p mutex_variable $1 = {__data = {__lock = 2, __count = 0, __owner = 1234, ...}} (gdb) x/3d &mutex_variable 0x6010a0: 2 0 1234 # __lock, __count, __owner ``` > `__owner`值表示持有锁的线程ID(LWP)[^1] ##### 5. 定位锁持有者 ```gdb (gdb) info threads # 查找LWP=1234的线程 Id Target Id Frame 2 Thread 1234 "my_thread" thread_func2() at example.cpp:25 (gdb) thread 2 (gdb) bt # 查看持有锁线程的堆栈 #0 thread_func2 () at example.cpp:25 #1 0x00007f... in std::execute_native_thread_routine(void*) () ``` ##### 6. 分析死锁链 ```gdb (gdb) thread apply all bt # 获取所有线程堆栈 ``` 比较各线程: - 线程1:等待锁A(被线程2持有) - 线程2:等待锁B(被线程1持有) → 形成循环等待[^1][^3] #### 三、死锁调试技巧 1. **条件断点**: ```gdb break 15 thread 2 if try_lock_count>5 # 当线程2尝试加锁超过5次时中断 ``` 2. **锁状态监控**: ```gdb watch *(pthread_mutex_t*)0x6010a0 # 监控特定锁的内存变化 ``` 3. **死锁预防检测**: ```gdb set print thread-events on # 显示线程创建/销毁事件 set scheduler-locking on # 锁定调度器(单步调试时) ``` #### 四、VSCode 调试配置(可选) 1. 创建`.vscode/launch.json`: ```json { "version": "0.2.0", "configurations": [ { "name": "GDB Debug", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/a.out", "stopAtEntry": false, "externalConsole": true, "MIMode": "gdb", "setupCommands": [ { "text": "-enable-pretty-printing", "description": "启用美化打印" } ] } ] } ``` 2. 调试时使用**并行堆栈视图**(Parallel Stacks)分析线程关系[^3] #### 五、死锁预防最佳实践 1. **锁顺序规则**: ```cpp // 所有线程按固定顺序获取锁 void thread_safe_operation() { std::lock_guard<std::mutex> lock1(mutexA); // 总是先A后B std::lock_guard<std::mutex> lock2(mutexB); } ``` 2. **使用`std::lock`原子操作**: ```cpp void safe_lock() { std::unique_lock<std::mutex> lockA(mutexA, std::defer_lock); std::unique_lock<std::mutex> lockB(mutexB, std::defer_lock); std::lock(lockA, lockB); // 原子化加锁(避免死锁) } ``` 3. **设置锁超时**: ```cpp std::timed_mutex mutex; if(mutex.try_lock_for(std::chrono::milliseconds(100))) { // 成功获取锁 } else { // 超时处理 } ``` > 通过上述方法,可定位到代码中形如以下死锁[^3]: > ```cpp > // 线程1:先锁m1后锁m2 > void thread1() { > lock_guard<mutex> g1(m1); > lock_guard<mutex> g2(m2); // 死锁点 > } > > // 线程2:先锁m2后锁m1 > void thread2() { > lock_guard<mutex> g2(m2); > lock_guard<mutex> g1(m1); // 死锁点 > } > ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值