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调试
-
使用
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 <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调试
-
编译运行Qt程序后,点击pushButton按键,进入QtConcurrent线程,触发死锁;
-
使用
ps -aux | grep 'testMutex\|USER'命令查看死锁进程pid; -
使用
sudo gdb -q -p 21714命令将gdb附加到进程; -
使用
info threads命令查看所有线程的信息; -
如下图所示,可看出线程7的类型为
Thread(pooled)(如果是使用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
* 邮箱: 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调试
-
使用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库的调试符号。
{__/}
(̷ ̷´̷ ̷^̷ ̷`̷)̷◞~❤
| ⫘ |
相关文章:
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、W…...
HugeGraph Hubble 配置 https 协议的操作步骤
背景 HugeGraph 图数据库的 Server 端支持 https 配置,官方文档中有说明相对比较容易,而 Hubble 部署过程都是 http的。 我们有一个应用要嵌入 hubble 页面,而且部署为 https ,那么 Hubble 是否支持配置 https 呢?网…...
大型应用的架构演进--spring家族在其中的作用
01 大型应用的架构演进 带来的挑战: 运维与监控 分布式带来的复杂性 接口的调整成本 测试成本 依赖管理成本 02 Spring家族 在我看来,springboot的3大特点(我常用的):内置的web容器;开箱即用的starter模版;自动配置&…...
LinkedHashMap 简单实现LRU
要使用 LinkedHashMap 来实现LRU(最近最少使用)缓存,可以设置它的访问顺序为true,以便在每次访问一个元素时,将它移到最后,从而实现LRU的特性。以下是一个简单的Java示例: import java.util.Li…...
mysql字符串函数
函数名 描述 示例 ASCII(s) 返回字符串s的第一个字符的ASCII码 返回CustomerName字段第一个字母的ASCII码: SELECT ASCII(CustomerName) AS NumCodeOfFirstChar FROM Customers; CHAR_LENGTH(s) 返回字符串s的字符数 返回字符串RUNOOB的字符数: …...
【强烈推荐】视频转gif、图片拼gif,嘎嘎好用,免费免费真的免费,亲测有效,无效过来打我
问题描述 最近遇到一个需求是需要将视频生成gif,这个看上去不是很难,所以有了以下的解决办法 解决办法 首先想到的当然是自己写一个,用了两套代码: from moviepy.editor import *# 读取视频文件 video_clip VideoFileClip(&quo…...
C# Onnx Yolov8 Detect 印章 指纹捺印 检测
应用场景 检测文件中的印章和指纹捺印,用于判断文件是否合规(是否盖章,是否按印) 效果 项目 代码 using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; using OpenCvSharp; using System; using System.…...
0034【Edabit ★☆☆☆☆☆】【修改Bug4】Buggy Code (Part 4)
0034【Edabit ★☆☆☆☆☆】【修改Bug4】Buggy Code (Part 4) bugs conditions strings Instructions Emmy has written a function that returns a greeting to users. However, she’s in love with Mubashir, and would like to greet him slightly differently. She add…...
第十五篇-推荐-Huggingface-镜像-2023-10
推荐一个Huggingface-镜像网站 可下载模型和数据集,解决Huggingface无法访问问题,希望可以一直使用 https://hf-mirror.com/ 举个栗子 https://hf-mirror.com/models?searchqwen 有时需要验证,按要求点就好 域名 hf-mirror.com…...
Macos文件图像比较工具:Kaleidoscope for Mac
Kaleidoscope是一款文件图像比较工具,它可以方便地比较两个文本或者图片文件的差异。这个工具可以在Mac系统上使用,并且支持多种文件格式,包括文本文件、图片文件、PDF文件等等。 Kaleidoscope有一个直观的用户界面,可以让用户轻…...
Docker搭建Plex流媒体服务并播放自己本地视频
Docker搭建Plex流媒体服务 安装Docker创建存储配置文件的目录创建Plex容器配置Plex设置媒体库访问Plex 1 介绍 Plex是一个流媒体服务器,可以轻松地将你的媒体文件库(如电影、电视节目和音乐)通过网络流式传输到各种设备上。 Plex 是一套媒体…...
idea + Docker-Compose 实现自动化打包部署(仅限测试环境)
一、修改docker.service文件,添加监听端口 vi /usr/lib/systemd/system/docker.service ExecStart/usr/bin/dockerd -H fd:// --containerd/run/containerd/containerd.sock -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock重启docker服务 systemctl daemo…...
ubuntu 下载Python
目前为止,Python 3.11 是最新版本的 Python。要在 Ubuntu 中下载和安装 Python 3.11,可以按照以下步骤进行: 安装编译所需的依赖项: sudo apt update sudo apt install -y build-essential zlib1g-dev libffi-dev libssl-dev libl…...
python 使用json包在json格式字符串和python对象之间的变化
起因:使用python json包时,将键值对均为数字的字典存入txt文件后重新加载进字典后出现“字典key值不唯一”的神奇现象。 相关代码: 字典添加数据部分 def xuhao_chuti(self):rand random.randint(1, 908)if rand in self.memery.keys() an…...
【C++】继承 ⑫ ( 继承的二义性 | virtual 虚继承 )
文章目录 一、继承的二义性1、场景说明 - 继承的二义性2、继承中的二义性报错3、完整代码示例 二、virtual 虚继承1、虚继承引入2、虚继承语法3、代码示例 - 虚继承 一、继承的二义性 1、场景说明 - 继承的二义性 A 类 是 父类 , B 类 和 C 类 继承 A 类 , 是 子类 , D 类 多…...
Linux网络流量监控iftop
在 Linux 系统下即时监控服务器的网络带宽使用情况,有很多工具,比如 iptraf、nethogs 等等,但是推荐使用小巧但功能很强大的 iftop 工具【官网:http://www.ex-parrot.com/~pdw/iftop/】。iftop 是 Linux 系统一个免费的网卡实时流…...
【虚幻引擎UE】UE4/UE5 基于2D屏幕坐标获取场景3D坐标 射线检测(蓝图/C++)
UE4/UE5 基于2D屏幕坐标获取场景3D坐标 一、射线检测1)定义1)射线与3D场景中的物体交互的流程2)射线检测蓝图函数3)蓝图实现根据鼠标点击位置获取场景中的坐标值4)根据相机中心点获取场景中的坐标值5)射线检…...
【OpenHarmony】系统编译环境搭建笔记
0、安装WSL 一定要安装WSL 2否则编译慢到怀疑人生。 1、将WSL从C盘迁移到其他盘 2、安装编译依赖库 按照上述流程,安装会提示一些错误,直接使用如下命令: sudo apt-get update && sudo apt-get install binutils binutils-dev g…...
深入理解JVM虚拟机第十二篇:JVM中的线程说明
文章目录 一:线程说明 1:线程概述 2:后台虚拟机主要线程 (一):虚拟机线程...
synchronized 、ReentrantLock
synchronized 和 ReentrantLock 都是用于实现多线程同步的机制: 锁的获取方式: synchronized 是内置的 Java 关键字,它通过对象的内置监视器来获取锁。每个对象都有一个关联的监视器,只有一个线程可以获得对象的监视器,其他线程必须等待。ReentrantLock 是一个类,它提供了…...
工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj,再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
MinIO Docker 部署:仅开放一个端口
MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...
人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent
安全大模型训练计划:基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标:为安全大模型创建高质量、去偏、符合伦理的训练数据集,涵盖安全相关任务(如有害内容检测、隐私保护、道德推理等)。 1.1 数据收集 描…...
