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 是一个类,它提供了…...
接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
调用支付宝接口响应40004 SYSTEM_ERROR问题排查
在对接支付宝API的时候,遇到了一些问题,记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...
免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...
云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...
push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
