重庆网络网站推广,全国住房和城乡建设厅官网,优秀vi设计案例分析ppt,房地产开发公司账务处理流程C(Qt)软件调试—线程死锁调试#xff08;15#xff09; 文章目录 C(Qt)软件调试---线程死锁调试#xff08;15#xff09;1、前言2、常见死锁3、linux下gdb调试C死锁1.1 使用代码1.2 gdb调试 3、linux下gdb调试Qt死锁1.1 使用代码1.2 gdb调试 4、Windows下gdb调试C死锁5、W…C(Qt)软件调试—线程死锁调试15 文章目录 C(Qt)软件调试---线程死锁调试151、前言2、常见死锁3、linux下gdb调试C死锁1.1 使用代码1.2 gdb调试 3、linux下gdb调试Qt死锁1.1 使用代码1.2 gdb调试 4、Windows下gdb调试C死锁5、Windows下gdb调试Qt死锁6、Windows下Windbg调试C死锁1.1 使用代码1.2 Windbg调试 7、Windows下Windbg调试Qt死锁 1、前言
死锁是一种情况其中两个或多个线程或进程相互等待对方释放资源导致它们都无法继续执行。这是一种非常令人头疼的问题因为它可以导致程序挂起无法继续运行。
本文中会详细讲述linux、Windows下调试C线程死锁、Qt线程死锁的方式。
系统环境ubuntu20.04、Windows10编译器g10、MinGW、MSVC2017-64调试工具gdb、WinDbg。所有程序编译时最好加上调试信息如果是使用Qt则使用Debug或者Profile模式。文中用到的方法也适用于调试死循环不过细节上有一点点区别。
2、常见死锁
单线程死锁 有时候线程申请了锁资源还没有等待释放又一次申请这把锁结果就是挂起等待这把锁的释放但是这把锁是被自己拿着所以就会永远挂起等待就造成了死锁。导致重复加锁的原因可能如下
通常会因为在多分支中加锁而某个分支忘记了加锁或者因为return、break等语句跳过了锁的释放因为程序中自己使用throw抛出异常或者底层库抛出异常打乱了程序的执行流程导致锁没有释放。
例如考虑以下伪代码
void threadFun1()
{g_mutex1.lock(); // 加锁g_mutex1.lock(); // 重复加锁g_mutex1.unlock();
}void threadFun1()
{g_mutex1.lock(); // 加锁if(value 10) {return; // 提前返回跳过释放}g_mutex1.unlock();
}void threadFun1()
{g_mutex1.lock(); // 加锁if(value 10) {throw; // 抛出异常打乱执行流程跳过释放}g_mutex1.unlock();
}多线程死锁 多线程死锁是更常见的情况通常在多个线程之间共享资源时发生也比单线程死锁更难排查。
多线程死锁是指两个或多个线程在等待对方释放资源时被阻塞无法继续执行。
例如线程1锁定了lock1并尝试获取lock2而线程2锁定了lock2并尝试获取lock1它们彼此等待对方释放资源从而导致死锁。
/********************************************************************************
* 文件名 main1.cpp
* 创建时间 2023-10-25 10:57:54
* 开发者 MHF
* 邮箱 1603291350qq.com
* 功能 多线程死锁示例
*********************************************************************************/
#include iostream
#include thread
#include mutex
#include unistd.husing namespace std;
mutex mutex1;
mutex mutex2;void threadA()
{cout 启动线程A endl;mutex1.lock();cout 线程A上锁mutex1 endl;// 为了模拟死锁让线程A休眠一段时间sleep(1);mutex2.lock(); // 由于线程B已经上锁mutex2这里会等待线程B解锁cout 线程A上锁mutex2 endl;// 执行一些操作...mutex2.unlock();mutex1.unlock();
}void threadB()
{cout 启动线程B endl;mutex2.lock();cout 线程B上锁 mutex2 endl;// 为了模拟死锁让线程B休眠一段时间sleep(1);mutex1.lock(); // 由于线程A已经上锁mutex1这里会等待线程A解锁cout 线程B上锁 mutex1 endl;// 执行一些操作...mutex1.unlock();mutex2.unlock();
}int main()
{thread t1(threadA);thread t2(threadB);t1.join();t2.join();return 0;
}
3、linux下gdb调试C死锁
1.1 使用代码 /********************************************************************************
* 文件名 main.cpp
* 创建时间 2023-10-24 21:40:05
* 开发者 MHF
* 邮箱 1603291350qq.com
* 功能 单线程死锁示例
*********************************************************************************/
#includeiostream
#include thread
#include mutexusing namespace std;mutex g_mutex1;void threadFun1()
{cout 1 endl;g_mutex1.lock(); // 加锁cout 2 endl;g_mutex1.lock(); // 重复加锁cout 3 endl;
}int main()
{thread t1(threadFun1);t1.join();return 0;
}1.2 gdb调试 使用g -g main.cpp -lpthread命令编译代码 使用./a.out运行程序会发现程序出现死锁不会继续执行 重新打开一个终端窗口 使用ps -aux | grep a.out\|USER命令查看a.out程序的进程信息注意\| 前后不能有空格 grep “a.out \| USER”表示只显示包含a.out字符串或者USER字符串的行 使用sudo gdb -q -p 14742将gdb附加到a.out的进程PID上注意附加到进程需要使用sudo 进入gdb后使用info threads命令查看所有线程的信息 从图中可以看出在线程2的堆栈停止在了**__lll_lock_wait**帧在这个位置使用了g_mutex1锁__lll_lock_wait函数是Linux系统中用于实现线程互斥锁等待的函数它使线程进入等待状态直到互斥锁可用。 使用thread 2命令进入到线程2中 使用bt命令查看线程2当前的堆栈信息也可以使用thread apply all bt命令查看所有线程的堆栈 可以堆栈停止在main.cpp文件的第21行threadFun1()函数中 使用f 4命令切换到线程2堆栈的第4帧可以看见是停止在g_mutex1.lock()这一行加锁的代码上 使用list命令查看上下文代码可以看见加锁了两次 使用p g_mutex1命令打印锁的信息可以看见__lock 2也是加锁了两次。
3、linux下gdb调试Qt死锁
1.1 使用代码
#include widget.h
#include ui_widget.h
#include QtConcurrent
#include QMutexQMutex g_mutex;Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui-setupUi(this);
}Widget::~Widget()
{delete ui;
}void Widget::on_pushButton_clicked()
{// 创建一个QtConcurrent线程QtConcurrent::run(QThreadPool::globalInstance(), [](){qDebug() 进入QtConcurrent线程;g_mutex.lock();qDebug() 加锁1次;g_mutex.lock();qDebug() 加锁2次重复加锁;g_mutex.unlock();});
}
1.2 gdb调试 编译运行Qt程序后点击pushButton按键进入QtConcurrent线程触发死锁 使用ps -aux | grep testMutex\|USER命令查看死锁进程pid 使用sudo gdb -q -p 21714命令将gdb附加到进程 使用info threads命令查看所有线程的信息 如下图所示可看出线程7的类型为Threadpooled如果是使用QThread创建的线程这里类型就是QThread这是使用线程池创建的QtConcurren线程停止的堆栈帧的状态为syscall()程序停在syscall()函数通常意味着它正在进行系统调用而如果出现死锁后线程就会一直处于这种状态 使用thread 7命令切换到线程7 使用bt命令查看线程7堆栈信息 如下图所示利用看出QBasicMutex::lockInternal()或者QMutex::lock()表示线程7堆栈停止在互斥锁的lock()函数位置如何找到包含自己源代码的堆栈帧在widget.cpp文件的29行。 使用f 3命令切换到堆栈的第3帧可以看的这一帧停止在g_mutex.lock()位置正在加锁位置 使用list命令查看上下文代码可以看出加锁两次 使用p g_mutex命令打印g_mutex锁的信息和c中的mutex锁不同QMutex锁打印无法获得有帮助的信息。
4、Windows下gdb调试C死锁 使用代码和linux下一样。 打开MinGW-64的cmd窗口从这里打开具有完整的环境变量便于找到依赖库 进入到源代码所在路径 使用g.exe main.cpp -g -lpthread命令编译代码如果提升找不到g则使用MinGw所在绝对路径 执行a.exe程序触发死锁 打开任务管理器找到a.exe程序右键选择【转到详细信息】查看进程的pid号 再打开一个cmd窗口 使用gdb -q -p 8740将gdb附加到进程调试 使用info threads命令查看所有线程信息和linux下不同不能直接看出死锁线程 使用thread apply all bt查看所有线程的堆栈信息 如下图所示可以看出在线程2中出现了pthread_mutex_lock()表示这个线程的堆栈停止在上锁位置所以出现死锁再往下找发现死锁位置出现在main.cpp文件的第21行中threadFun1()函数位置。 后面操作就可有可无了并且和linux下没有什么区别 5、Windows下gdb调试Qt死锁 使用代码和linux下的相同 注意Windows下使用MinGW编译程序调试时选择的gdb版本应该和编译的g版本相同不能使用32位的gdb调试64位的程序或者相反。 Qt编译运行程序后触发死锁 打开对应版本的MinGW的cmd终端 使用任务管理器窗口死锁程序的pid进程号 使用gdb -q -p pid将gdb附加到死锁进程 直接使用thread apply all bt显示所有线程的堆栈信息 可以看出线程3出现死锁后续操作都是一样的。 不过MinGW中gdb调试有时会出现下列情况无法进行调试目前没找到问题
6、Windows下Windbg调试C死锁
1.1 使用代码
直接使用C中的mutex锁重复上锁在msvc编译器中会在触发时抛出异常所以无需调试。这里改为使用多线程死锁进行演示。
/********************************************************************************
* 文件名 main.cpp
* 创建时间 2023-10-25 10:57:54
* 开发者 MHF
* 邮箱 1603291350qq.com
* 功能 多线程死锁示例
*********************************************************************************/
#include iostream
#include thread
#include mutex
#include Windows.husing namespace std;
mutex mutex1;
mutex mutex2;void threadA()
{cout start A endl;mutex1.lock();cout threadA mutex1 lock endl;// 为了模拟死锁让线程A休眠一段时间Sleep(1000);mutex2.lock(); // 由于线程B已经上锁mutex2这里会等待线程B解锁cout threadA mutex2 lock endl;// 执行一些操作...mutex2.unlock();mutex1.unlock();
}void threadB()
{cout start B endl;mutex2.lock();cout threadB mutex2 lock endl;// 为了模拟死锁让线程B休眠一段时间Sleep(1000);mutex1.lock(); // 由于线程A已经上锁mutex1这里会等待线程A解锁cout threadB mutex1 lock endl;// 执行一些操作...mutex1.unlock();mutex2.unlock();
}int main()
{thread t1(threadA);thread t2(threadB);t1.join();t2.join();return 0;
}
1.2 Windbg调试 使用MSVC编译器编译代码运行并触发死锁 打开WinDbg程序在C:\Program Files\Windows Kits\10\Debuggers\x64路径下 选择【File】-【Attach to Process】或者直接按快捷键F6 然后选择By ID找到死锁进程然后点击【OK】 然后输入~*k命令查看所有线程的堆栈信息如下所示出现std::_Mutex_base::lock字样可看出在线程1、2出现死锁 然后选择【View】打开【Processes and Threads】窗口和【Calls Stack】窗口 点击【Processes and Threads】窗口中的线程1再点击【Calls Stack】窗口中的堆栈帧就可以跳转到出现死锁的源码位置 或者直接点击Command窗口中的堆栈帧也可以跳转到死锁源码位置不过在WinDbg中定位到源码的位置是实际位置的下一行。 7、Windows下Windbg调试Qt死锁 使用代码和Linux下的相同 前面步骤都是相同的 在使用~*k命令窗口所有线程的堆栈信息时会发现看不到太多有帮助的信息这时可用找包含源码文件的堆栈帧 如图所示点击这一帧就可以跳转到源码查看是否时出现死锁的位置 如果想要查看更加详细的调试信息需要到Qt官网下载Qt库的调试符号。
{__/} (̷ ̷´̷ ̷^̷ ̷̷)̷◞~❤ | ⫘ |