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库的调试符号。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Selenium+Pytest自动化测试框架详解

前言 selenium自动化 pytest测试框架 本章你需要 一定的python基础——至少明白类与对象&#xff0c;封装继承&#xff1b;一定的selenium基础——本篇不讲selenium&#xff0c;不会的可以自己去看selenium中文翻译网 一、测试框架简介 测试框架有什么优点 代码复用率高&…

【LeetCode】5. 最长回文子串

题目链接 文章目录 Python3方法&#xff1a; 暴力求解 ⟮ O ( n 3 ) 、 O ( 1 ) ⟯ \lgroup O(n^3)、O(1)\rgroup ⟮O(n3)、O(1)⟯方法一&#xff1a; 动态规划 (回文串同时去掉头尾后 依然是回文串) ⟮ O ( n 2 ) ⟯ \lgroup O(n^2)\rgroup ⟮O(n2)⟯⭐ 方法二&#xff1a;…

网工内推 | 急招网工,思科、华为认证优先,法定节假日三薪

01 江苏臻云技术 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1、负责落实数据中心机房日常网络监测及巡检任务&#xff1b; 2、负责数据中心网络设备日常监控、变更、维护、巡检&#xff1b; 3、负责日常巡检报告、故障维护报告、变更申请的文档的编制&#xff1b;…

番外8.2 --- 后续

### 01&#xff1a;dd命令&#xff1a;在新挂载点创建swap文件大小10MB&#xff1b;&#xff08;dd if/dev/zero of/swap bs1024 count10240&#xff09; 02&#xff1a;给swap建立文件系统&#xff0c;将其分属到swap文件&#xff08;mkswap /swap&#xff1b; swapon /swap &…

下载视频号安装,下载视频号安装到手机上?

在数字化时代&#xff0c;随着社交媒体的蓬勃发展&#xff0c;视频内容正成为品牌传播和用户吸引的重要方式。而作为当下最热门的短视频平台之一&#xff0c;视频号为用户提供了创作、分享和推广优质内容的机会。如果您还不了解视频号视频或想进一步了解如何下载视频号视频&…

多测师肖sir_高级金牌讲师__接口测试之练习题(6.1)

常见的接口面试题目: 1.postman接口测试&#xff0c;它有一个功能可以设置参数化&#xff0c;你有用过吗? 用过 &#xff08;1&#xff09;新建一个csv.文件 填写user、pwd 新建一个全局变量 user、pwd 点击bodyform-data 填写user、pwd 点击run 导入csv.件 查看结果 &#x…

element-ui vue2 iframe 嵌入外链新解

效果如图 实现原理 在路由中通过 props 传值 {path: /iframe,component: Layout,meta: { title: 小助手, icon: example },children: [{path: chatglm,name: chatglm,props: { name: chatglm,url: https://chatglm.cn },component: () > import(/views/iframe/common),me…

Facebook广告效果数据获取

一、背景 公司每年在Facebook和Google上投放了大量的广告&#xff0c;我总不能让老板登录Facebook广告投放平台上去看广告效果&#xff0c;其实老板只关注每天花了多少钱引来了多少客户&#xff0c;每个客户平均花费多少钱&#xff0c;其它的他才不关心&#xff0c;有Facebook…

如何将Mysql数据库的表导出并导入到另外的架构

如何将Mysql数据库的表导出并导入到另外的架构 准备一、解决方法1.右键->导出->用mysqldump导出2.注意路径一般为&#xff1a;C:/Program Files/MySQL/MySQL Server 8.0/bin/mysqldump.exe和导出的sql文件位置3.右键->SQL脚本->运行SQL脚本4.找到SQL脚本并点击确定…

C++基础:函数模板

为了代码重用&#xff0c;代码必须是通用的&#xff1b;通用的代码就必须不受数据类型的限制。那么我们可以把数据类型改为一个设计参数&#xff0c;这种类型的程序设计称为参数化程序设计&#xff0c;软件模板有模板构造&#xff0c;包括函数模板和类模板。 函数模板可以用来…

Facebook账号被封?那是因为没做对这些事

Facebook是全球最大的社交媒体平台之一&#xff0c;拥有数十亿的全球用户。它的主要产品包括Facebook&#xff08;面向个人用户的社交媒体平台&#xff09;、Instagram、WhatsApp和Messenger。同时他也是美国数字广告市场的主要参与者之一&#xff0c;其广告平台吸引了数百万广…

H5营销观察:H5破圈传播有什么秘诀

在移动互联网时代&#xff0c;流量越加碎片化&#xff0c;场景变得相对短促和兴趣导向&#xff0c;一个营销H5产生的每一次点击、每一次互动、每一次流量停留背后都会有相应的动机&#xff0c;也是营销流量效果的成因。 今天&#xff0c;我们一起来探究下什么样的内容更容易传播…

微信小程序:点击按钮出现右侧弹窗

效果 代码 wxml <!-- 弹窗信息 --> <view class"popup-container" wx:if"{{showPopup}}"><view class"popup-content"><!-- 弹窗内容 --><text>这是一个右侧弹窗</text></view> </view> <…

系列十四、Spring如何处理线程安全问题

一、线程安全问题出现的原因 Spring中出现线程安全的原因是&#xff0c;单实例bean中存在成员变量&#xff0c;并且有对这个bean进行读写的操作&#xff0c;因此出现了线程安全的问题。 二、案例代码 2.1、MySpringConfig /*** Author : 一叶浮萍归大海* Date: 2023/10/24 1…

【软考】系统集成项目管理工程师(九)项目成本管理【4分】

一、成本概念 1、产品全生命周期成本 产品或系统的整个使用生命周期内&#xff0c;在获得阶段&#xff08;设计、生产、安装和测试等活动&#xff0c;即项目存续期间&#xff09;、运营与维护、生命周期结束时对产品的处置所发生的全部成本 2、成本类型 成本类型描述可变成…

基于 ARM+FPGA+AD平台的多类型同步信号采集仪开发及试验验证(二)板卡总体设计

2.2 板卡总体设计 本章开发了一款基于 AD7193RJ45 的多类型传感信号同步调理板卡&#xff0c;如图 2.4 所 示&#xff0c;负责将传感器传来的模拟电信号转化为数字信号&#xff0c;以供数据采集系统采集&#xff0c;实现了 单通道自由切换传感信号类型与同步采集多类型传…

在进行自动化测试,遇到验证码的问题,怎么办?

1.找开发去掉验证码或者使用万能验证码 2.使用OCR自动识别 使用OCR自动化识别&#xff0c;一般识别率不是太高&#xff0c;处理一般简单验证码还是没问题 这里使用的是Tesseract-OCR,下载地址&#xff1a;https://github.com/A9T9/Free-Ocr-Windows-Desktop/releases 怎么使…

北邮22级信通院数电:Verilog-FPGA(7)第七周实验(1):带使能端的38译码器全加器(关注我的uu们加群咯~)

北邮22信通一枚~ 跟随课程进度更新北邮信通院数字系统设计的笔记、代码和文章 持续关注作者 迎接数电实验学习~ 获取更多文章&#xff0c;请访问专栏&#xff1a; 北邮22级信通院数电实验_青山如墨雨如画的博客-CSDN博客 关注作者的uu们可以进群啦~ 目录 方法一&#xff…

huoshan device_id和iid生成记录学习分析

huoshan 和 douyin 作为字节系的产品&#xff0c;device_id 和 iid是抓包经常遇到的字段&#xff0c;代表了设备的概念。 还是熟悉的接口&#xff0c;像 device_register &#xff0c;app_alert_check 和 app_notice_status 都需要请求一遍。 这些接口跑完一遍&#xff0c;设…

信息科技风险管理:合规管理、技术防控与数字化

⭐简单说两句⭐ 作者&#xff1a;后端小知识 CSDN个人主页&#xff1a;后端小知识 &#x1f50e;GZH&#xff1a;后端小知识 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; 信息科技对金融业务发展所起的作用是举足轻重的。近年来&#xff0c…