C++(Qt)软件调试---线程死锁调试(15)

C++(Qt)软件调试—线程死锁调试(15)

文章目录

  • C++(Qt)软件调试---线程死锁调试(15)
    • 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、Windows下gdb调试Qt死锁
    • 6、Windows下Windbg调试C++死锁
      • 1.1 使用代码
      • 1.2 Windbg调试
    • 7、Windows下Windbg调试Qt死锁

1、前言

死锁是一种情况,其中两个或多个线程(或进程)相互等待对方释放资源,导致它们都无法继续执行。这是一种非常令人头疼的问题,因为它可以导致程序挂起,无法继续运行。

本文中会详细讲述linux、Windows下调试C++线程死锁、Qt线程死锁的方式。

  • 系统环境:ubuntu20.04、Windows10;
  • 编译器:g++10、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
* 邮箱:     1603291350@qq.com
* 功能:     多线程死锁示例
*********************************************************************************/
#include <iostream>
#include <thread>
#include <mutex>
#include <unistd.h>using 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
* 邮箱:     1603291350@qq.com
* 功能:     单线程死锁示例
*********************************************************************************/
#include<iostream>
#include <thread>
#include <mutex>using 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调试

  1. 使用g++ -g main.cpp -lpthread命令编译代码;

  2. 使用./a.out运行程序,会发现程序出现死锁,不会继续执行;

    在这里插入图片描述

  3. 重新打开一个终端窗口;

  4. 使用ps -aux | grep "a.out\|USER"命令查看a.out程序的进程信息(注意:\| 前后不能有空格);

    • grep “a.out \| USER”:表示只显示包含a.out字符串或者USER字符串的行;

    在这里插入图片描述

  5. 使用sudo gdb -q -p 14742将gdb附加到a.out的进程PID上(注意附加到进程需要使用sudo);

  6. 进入gdb后使用info threads命令查看所有线程的信息;

    在这里插入图片描述

  7. 从图中可以看出在线程2的堆栈停止在了**__lll_lock_wait**帧,在这个位置使用了g_mutex1锁,__lll_lock_wait函数是Linux系统中用于实现线程互斥锁等待的函数,它使线程进入等待状态,直到互斥锁可用。

  8. 使用thread 2命令进入到线程2中;

  9. 使用bt命令查看线程2当前的堆栈信息(也可以使用thread apply all bt命令查看所有线程的堆栈);

    在这里插入图片描述

  10. 可以堆栈停止在main.cpp文件的第21行,threadFun1()函数中;

  11. 使用f 4命令切换到线程2堆栈的第4帧,可以看见是停止在g_mutex1.lock()这一行加锁的代码上;

  12. 使用list命令查看上下文代码,可以看见加锁了两次;

  13. 使用p g_mutex1命令打印锁的信息可以看见__lock = 2也是加锁了两次。

    在这里插入图片描述

3、linux下gdb调试Qt死锁

1.1 使用代码

#include "widget.h"
#include "ui_widget.h"
#include <QtConcurrent>
#include <QMutex>QMutex 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调试

  1. 编译运行Qt程序后,点击pushButton按键,进入QtConcurrent线程,触发死锁;

  2. 使用ps -aux | grep 'testMutex\|USER'命令查看死锁进程pid;

  3. 使用sudo gdb -q -p 21714命令将gdb附加到进程;

  4. 使用info threads命令查看所有线程的信息;

  5. 如下图所示,可看出线程7的类型为Thread(pooled)(如果是使用QThread创建的线程这里类型就是QThread),这是使用线程池创建的QtConcurren线程,停止的堆栈帧的状态为syscall();程序停在syscall()函数通常意味着它正在进行系统调用,而如果出现死锁后线程就会一直处于这种状态;

    在这里插入图片描述

  6. 使用thread 7命令切换到线程7;

  7. 使用bt命令查看线程7堆栈信息;

  8. 如下图所示,利用看出QBasicMutex::lockInternal()或者QMutex::lock(),表示线程7堆栈停止在互斥锁的lock()函数位置,如何找到包含自己源代码的堆栈帧,在widget.cpp文件的29行。

    在这里插入图片描述

  9. 使用f 3命令切换到堆栈的第3帧,可以看的这一帧停止在g_mutex.lock()位置,正在加锁位置;

  10. 使用list命令查看上下文代码,可以看出加锁两次;

  11. 使用p g_mutex命令打印g_mutex锁的信息,和c++中的mutex锁不同,QMutex锁打印无法获得有帮助的信息。

    在这里插入图片描述

4、Windows下gdb调试C++死锁

使用代码和linux下一样。

  1. 打开MinGW-64的cmd窗口(从这里打开具有完整的环境变量,便于找到依赖库);

在这里插入图片描述

  1. 进入到源代码所在路径;

  2. 使用g++.exe main.cpp -g -lpthread命令编译代码(如果提升找不到g++则使用MinGw所在绝对路径);

  3. 执行a.exe程序,触发死锁;

在这里插入图片描述

  1. 打开任务管理器,找到a.exe程序,右键选择【转到详细信息】,查看进程的pid号,

在这里插入图片描述

  1. 再打开一个cmd窗口;

  2. 使用gdb -q -p 8740将gdb附加到进程调试;

  3. 使用info threads命令查看所有线程信息(和linux下不同,不能直接看出死锁线程);

在这里插入图片描述

  1. 使用thread apply all bt查看所有线程的堆栈信息;

  2. 如下图所示可以看出在线程2中出现了pthread_mutex_lock(),表示这个线程的堆栈停止在上锁位置,所以出现死锁,再往下找发现死锁位置出现在main.cpp文件的第21行中,threadFun1()函数位置。

    在这里插入图片描述

  3. 后面操作就可有可无了,并且和linux下没有什么区别;

在这里插入图片描述

5、Windows下gdb调试Qt死锁

使用代码和linux下的相同;

注意:Windows下使用MinGW编译程序,调试时选择的gdb版本应该和编译的g++版本相同,不能使用32位的gdb调试64位的程序,或者相反。

  1. Qt编译运行程序后,触发死锁;

  2. 打开对应版本的MinGW的cmd终端;

  3. 使用任务管理器窗口死锁程序的pid进程号;

  4. 使用gdb -q -p pid将gdb附加到死锁进程;

  5. 直接使用thread apply all bt显示所有线程的堆栈信息;

    在这里插入图片描述

  6. 可以看出线程3出现死锁,后续操作都是一样的。

  7. 不过MinGW中gdb调试有时会出现下列情况,无法进行调试,目前没找到问题;

    在这里插入图片描述

6、Windows下Windbg调试C++死锁

1.1 使用代码

  • 直接使用C++中的mutex锁重复上锁在msvc编译器中会在触发时抛出异常,所以无需调试。
  • 这里改为使用多线程死锁进行演示。
/********************************************************************************
* 文件名:   main.cpp
* 创建时间: 2023-10-25 10:57:54
* 开发者:   MHF
* 邮箱:     1603291350@qq.com
* 功能:     多线程死锁示例
*********************************************************************************/
#include <iostream>
#include <thread>
#include <mutex>
#include <Windows.h>using 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调试

  1. 使用MSVC编译器编译代码,运行并触发死锁;

  2. 打开WinDbg程序,(在C:\Program Files\Windows Kits\10\Debuggers\x64路径下);

  3. 选择【File】->【Attach to Process】或者直接按快捷键F6;

    在这里插入图片描述

  4. 然后选择By ID,找到死锁进程,然后点击【OK】;

    在这里插入图片描述

  5. 然后输入~*k命令查看所有线程的堆栈信息,如下所示出现std::_Mutex_base::lock字样,可看出在线程1、2出现死锁;

在这里插入图片描述

  1. 然后选择【View】,打开【Processes and Threads】窗口和【Calls Stack】窗口;

  2. 点击【Processes and Threads】窗口中的线程1,再点击【Calls Stack】窗口中的堆栈帧,就可以跳转到出现死锁的源码位置;

在这里插入图片描述

  1. 或者直接点击Command窗口中的堆栈帧也可以跳转到死锁源码位置(不过在WinDbg中定位到源码的位置是实际位置的下一行)。

在这里插入图片描述

7、Windows下Windbg调试Qt死锁

使用代码和Linux下的相同;

  1. 前面步骤都是相同的;

  2. 在使用~*k命令窗口所有线程的堆栈信息时会发现看不到太多有帮助的信息,这时可用找包含源码文件的堆栈帧;

在这里插入图片描述

  1. 如图所示,点击这一帧就可以跳转到源码查看是否时出现死锁的位置;

在这里插入图片描述

  1. 如果想要查看更加详细的调试信息,需要到Qt官网下载Qt库的调试符号。

{__/}
(̷ ̷´̷ ̷^̷ ̷`̷)̷◞~❤
| ⫘ |

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/117933.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c++ 并发与多线程(12)线程安全的单例模式-1

一、什么是线程安全 在拥有共享数据的多条数据并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。 二、如何保证线程安全 法1、给共享的资源加把锁,保证每个资源变量每时每刻至多被一个线程占用; 法2、让线…

leetcode做题笔记202. 快乐数

编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。如果这个过程 结果为 1&#xff0c…

【unaipp】tabBar配置/tabBar图标无法显示

bug&#xff1a;注意list配置iconfont我们自定义的图标就会无法显示 "tabBar": {"color": "#7A7E83","selectedColor": "#3cc51f","borderStyle": "black","backgroundColor": "#ffffff…

Android | Handler

Handler 的主要使用场景 子线程完成耗时操作的过程中&#xff0c;通过 Handler 向主线程发送消息 Message&#xff0c;用来更新 UI 界面。因为 Android 是在主线程中更新 UI 的&#xff0c;在主线程出现耗时操作时&#xff0c;就会导致用户界面卡顿&#xff0c;所以我们一般都…

javascript 设计模式 ( 读书笔记 )

javascript 设计模式 电子书链接 余杭子曰 用对象收编变量,防止变量覆盖和变量污染 let checkObject {checkEmail: function () {console.log("邮箱校验");},checkPhone: function () {console.log("手机号校验");},checkPasswork: function () {console…

【Linux】psplash制作Linux开机动画

1. 下载psplash软件 下载psplash源码到ubuntu中&#xff1a; 下载地址&#xff1a;https://git.yoctoproject.org/psplash/commit/安装依赖环境 sudo apt-get install libgdk-pixbuf2.0-dev2. 准备图片 开机动画静态图片&#xff1a;psplash-poky.png开机动画进度条图片&…

哪些车企AEB标配率「不及格」

对于汽车智能化来说&#xff0c;基础安全不分高低配。但实际情况&#xff0c;却是另一番景象。 在全球范围&#xff0c;目前不少国家及地区的监管机构正在考虑将AEB&#xff08;紧急制动系统&#xff09;作为乘用车的标配纳入法规&#xff0c;“这是道路安全向前迈出了重要的一…

2023年9月青少年机器人技术(三级)等级考试试卷-理论综合

2023年9月青少年机器人技术等级考试&#xff08;三级&#xff09;理论综合试卷 单选题 第 1 题 单选题 Arduino Nano主控板&#xff0c;通过光敏电阻控制LED灯亮度的变化。电路搭设及程序如下图所示&#xff0c;当光照强度逐渐增强时&#xff0c;LED的亮度逐渐减弱&#xff…

CentOS 7设置固定IP地址

当我们安装了一个虚拟机或者装了一个系统的时候&#xff0c;经常会遇到需要设置固定ip的情况&#xff0c;本文就以Centos 7为例&#xff0c;讲述如何修改固定IP地址。 1、用ifconfig命令查看使用的网卡 如上图所示&#xff0c;我们就会看到我们目前使用的网卡名称 2、编辑网卡…

Systemd服务内存占用高的处理

参考文章 ### https://blog.csdn.net/weixin_44821644/article/details/121095406## https://blog.csdn.net/c123m/article/details/124301104 现象 检查 操作系统是4C8G&#xff0c;systemd的内存使用率比较高。操作系统日志没看到异常。很多服务通过systemd托管 ## 检查有…

(三)(Driver)驱动开发之双机调试环境搭建及内核驱动的运行

文章目录 1. 驱动开发环境搭建2. 驱动开发新建项目及项目属性配置和编译3. 双机调试环境搭建3.1 安装虚拟机VMware3.2 配置Dbgview.exe工具3.3 基于Windbg的双机调试 4. 内核驱动的运行4.1 临时关闭系统驱动签名校验4.2 加载驱动 1. 驱动开发环境搭建 请参考另一篇:https://bl…

Oracle11gr2 + plsql 配置

一、在Oracle中使用cmd窗口进行imp导入文件时&#xff0c;有时会报错IMP-00000: 未成功终止导入。将cmd窗口使用管理员运行&#xff0c;在进行imp导入文件时&#xff0c;又会报imp不是内部或外部命令,也不是可运行的程序。针对这种问题&#xff0c;是环境变量没配置好的原因 1…

7. Cesium中的Primitive

1. Primitive 介绍 在 Cesium 中&#xff0c;Primitive 是一种基本的图元&#xff0c;用于呈现 3D 场景中的几何形状、材质和其他属性。 Primitive 由两个部分组成&#xff0c;一个是几何形状&#xff08;Geometry&#xff09;&#xff0c;用于定义 Primitive 的结构&#xf…

Guava-RateLimiter详解

简介&#xff1a; 常用的限流算法有漏桶算法和令牌桶算法&#xff0c;guava的RateLimiter使用的是令牌桶算法&#xff0c;也就是以固定的频率向桶中放入令牌&#xff0c;例如一秒钟10枚令牌&#xff0c;实际业务在每次响应请求之前都从桶中获取令牌&#xff0c;只有取到令牌的请…

Unity Spine 指定导入新Spine动画的默认材质

指定导入新Spine动画的默认材质 找到Spine的Editor导入配置如何修改方法一: 你可以通过脚本 去修改Assets/Editor/SpineSettings.asset文件方法二&#xff1a;通过面板手动设置 找到Spine的Editor导入配置 通常在 Assets/Editor/SpineSettings.asset 配置文件对应着 Edit/Prefe…

k8s简介以及各个组件

Kubernetes 概述 1、K8S 是什么&#xff1f; K8S 的全称为 Kubernetes (K12345678S)&#xff0c;PS&#xff1a;“嘛&#xff0c;写全称也太累了吧&#xff0c;不如整个缩写”。 作用&#xff1a; 用于自动部署、扩展和管理“容器化&#xff08;containerized&#xff09;应用…

wxPython 布局调试技巧

在Show()与MainLoop()直接加入以上代码 import wx.lib.inspection ...frame.Show() wx.lib.inspection.InspectionTool().Show() app.MainLoop()启动后会弹出布局查看工具

蓝桥杯每日一题2032.10.24

蓝桥杯大赛历届真题 - C 语言 B 组 - 蓝桥云课 (lanqiao.cn) 题目描述 题目分析 由于布局为两个字节为一行&#xff0c;那我们输入两个数就为一行&#xff0c;但是这两个数全部得用二进制进行表示使用bitset bitset:将一个数转化为二进制 bitset<8>:将一个数转化为8位…

【Unity3D】Unity与Android交互

1 Unity 发布 apk 1.1 安装 Android Build Support 在 Unity Hub 中打开添加模块窗口&#xff0c;操作如下。 选择 Android Build Support 安装&#xff0c;如下&#xff08;笔者这里已安装过&#xff09;。 创建一个 Unity 项目&#xff0c;依次点击【File→Build Settings→…

springboot maven项目环境搭建idea

springboot maven项目环境搭建idea 文章目录 springboot maven项目环境搭建idea用到的软件idea下载和安装java下载和安装maven下载和安装安装maven添加JAVA_HOME路径&#xff0c;增加JRE环境修改conf/settings.xml&#xff0c;请参考以下 项目idea配置打开现有项目run或build打…