Qt案例 滥用[Qt::BlockingQueuedConnection]队列链接导致出现程序死锁Bug的问题

记录项目开发过程中,使用QThread线程连接信号时,频繁使用Qt::BlockingQueuedConnection 队列连接造成的程序死锁。以及还原类机构解决问题过程。

目录导读

    • 方向一:bug问题描述
    • 方向二:bug解决过程
      • 还原造成死锁bug异常的类结构:
        • 创建 Dal_DownData 下载类
        • 创建 QThread_Down 线程基类
        • 创建 QThread_Operation 线程类
      • 还原造成死锁bug异常的具体操作:
    • 方向三:bug经验教训

方向一:bug问题描述

就在最近的开发过程中,遇到一个需要在线程中先下载数据,再对下载的数据进行一系列处理的需求,
在实际开发中,我创建了一个下载类 Dal_DownData 用来下载服务器数据,
创建了一个线程基类 QThread_Down 用来处理下载和实现一系列方法。
又创建了一个线程类 QThread_Operation 继承 QThread_Down 类 用来选择实际需要执行的方法。
实际流程就是Dal_DownData 下载类 把下载的进度信号传递给 线程基类 QThread_Down ,用 线程类 QThread_Operation 与界面绑定的进度条信号。

然而在实际使用过程中出现了
Qt: Dead lock detected while activating a BlockingQueuedConnection
的异常BUG,导致软件直接崩溃。
查看QBreakpad监控异常,发现没有生成任何异常dmp文件…
当时也没发现是Qt::BlockingQueuedConnection 队列连接的问题,
于是在网上找各种资料,耗时一天半,
最后用回调函数传递信号的方式解决了…


最初我以为是 下载类传达信号给线程类中转信号的时候造成的线程死锁, ,觉得这个例子比较典型在准备抽空还原这个Bug异常参与话题写个随笔的时候,通过还原类的结构发现,只需要不使用Qt::BlockingQueuedConnection 队列连接就不会有任何问题…
那一瞬间我真的是我有橘麻麦皮不知当浆不当浆。

方向二:bug解决过程

还原造成死锁bug异常的类结构:

  • 创建 Dal_DownData 下载类

Dal_DownData.h:


//! 定义一个下载类 伪代码
class Dal_DownData:public QObject
{
    Q_OBJECT
public:
    Dal_DownData();

    void StartDown(QString file="text");

Q_SIGNALS:
    ///开始下载
    void IsStart(bool bol);
    /// 直接进度条设置业务值(百分值)
    void ProgressBar(int value);
    /// 直接进度条设置业务值(百分值)
    void StyleStr(QString type);
};

Dal_DownData.h:

#include <QDebug>

Dal_DownData::Dal_DownData()
{

}

void Dal_DownData::StartDown(QString file)
{
    emit IsStart(true);
    for(int i=1;i<=100;i++)
    {
        //! 作为下载的一个进度效果
        emit ProgressBar(i);
        if(PROGRESSBAR!=nullptr)
            PROGRESSBAR(i);
        emit StyleStr("ACTIVE");
//        std::this_thread::sleep_for(std::chrono::seconds(1));
        Sleep(500);
    }
    emit IsStart(false);
}

  • 创建 QThread_Down 线程基类

QThread_Down.h

//! 一个下载线程
class QThread_Down:public QThread
{
    Q_OBJECT
public:
    QThread_Down(QObject* parent=nullptr);

    //! 修改操作1
    void Function1();

    //! 修改操作2
    void Function2();
Q_SIGNALS:
    ///开始
    void IsStart(bool bol);
    void SendMessStr(QString str);
protected:
    Dal_DownData* Down=nullptr;
};

QThread_Down.cpp

QThread_Down::QThread_Down(QObject* parent)
    :QThread(parent)

{
    Down=new Dal_DownData();
    connect(Down,&Dal_DownData::IsStart,this,[&](bool bol){
        if(bol)
            emit QThread_Down::SendMessStr("开始下载...");
        else
            emit QThread_Down::SendMessStr("下载结束...");
    },Qt::BlockingQueuedConnection);
    connect(Down,&Dal_DownData::ProgressBar,this,[&](int val){
        emit QThread_Down::SendMessStr(QString("\r正在下载:[%1%]...").arg(val));
    },Qt::BlockingQueuedConnection);
    connect(Down,&Dal_DownData::StyleStr,this,[&](QString val){
        emit QThread_Down::SendMessStr(QString("\r下载状态:[%1]...").arg(val));
    },Qt::BlockingQueuedConnection);

}

void QThread_Down::Function1()
{
    msleep(500);
}

void QThread_Down::Function2()
{
    msleep(500);
}
  • 创建 QThread_Operation 线程类

线程类 QThread_Operation 继承 QThread_Down 线程类。
QThread_Operation.h

class QThread_Operation:public QThread_Down
{
    Q_OBJECT
public:
    QThread_Operation(QObject* parent=nullptr);

    void run() override;

};

QThread_Operation.cpp

QThread_Operation::QThread_Operation(QObject* parent)
    :QThread_Down(parent)
{

}
void QThread_Operation::run()
{
    emit IsStart(true);
    emit SendMessStr("开始线程...");
    emit SendMessStr("等待三秒后开始调用下载...");
//    std::this_thread::sleep_for(std::chrono::seconds(3));
    msleep(500);

    Down->StartDown();


    emit SendMessStr("选择需要执行的操作等等...");
    Function1();
//    std::this_thread::sleep_for(std::chrono::seconds(1));
    msleep(500);

    emit SendMessStr("结束线程...");
    emit IsStart(false);
}

还原造成死锁bug异常的具体操作:

在测试的时候我是使用的控制台程序输出结果:
最开始的时候,我正常连接信号,因为控制台程序没有this变量,所以就没有修改连接信号槽的方式。

    QThread_Operation* operation=new QThread_Operation() ;
    QObject::connect(operation,&QThread_Operation::IsStart,[&](bool bol){
        if(bol)
            qDebug()<<"线程启动!";
        else
            qDebug()<<"线程结束!";
    });
    QObject::connect(operation,&QThread_Operation::SendMessStr,[&](QString Str){
        qDebug().noquote()<<Str;
    });
    operation->start();

结果正常输出结果,没有出现死锁,
我以为是我改用了 std::this_thread::sleep_for(std::chrono::seconds(3)) 暂停线程的问题,于是改用MScv编译器使用 Sleep(500) 暂停线程。
结果输出结果依旧正常。
于是修改进度大小,添加中转的信号个数,依旧没有问题,
返回项目环境测试还是有死锁,不是偶发事件。继续修改测试
直到我为了与原版内容结构一致,添加了Qt::BlockingQueuedConnection信号

//a是指QCoreApplication a(argc, argv);
QObject::connect(operation,&QThread_Operation::SendMessStr,&a,[&](QString Str){
        qDebug().noquote()<<Str;
    },Qt::BlockingQueuedConnection);

出现死锁:
Qt: Dead lock detected while activating a BlockingQueuedConnection: Sender is QThread_Operation(0x22b5ce12b90), receiver is QCoreApplication(0x9a027ef870)
有点疑惑,自认为对于Qt::BlockingQueuedConnection连接方式我还是有点心得的,还相到还能出个岔子,直到我找到了以前文章中摘抄的一段关于信号连接类型的说明:

Qt::BlockingQueuedConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。
出自:QT 面试题 个人标注重点

于是我移除Dal_DownData类与QThread_Down类绑定的中转信号的连接方式

    Down=new Dal_DownData();
    connect(Down,&Dal_DownData::IsStart,this,[&](bool bol){
        if(bol)
            emit QThread_Down::SendMessStr("开始下载...");
        else
            emit QThread_Down::SendMessStr("下载结束...");
    });
    connect(Down,&Dal_DownData::ProgressBar,this,[&](int val){
        emit QThread_Down::SendMessStr(QString("\r正在下载:[%1%]...").arg(val));
    });
    connect(Down,&Dal_DownData::StyleStr,this,[&](QString val){
        emit QThread_Down::SendMessStr(QString("\r下载状态:[%1]...").arg(val));
    });

还是死锁,只有移除QThread_Operation线程类与控制台程序的 Qt::BlockingQueuedConnection 连接方式,才正常输出。
我估摸着信号的接收者和发送者也没在同一线程上,咋还成死锁了,
盲猜可能就是QThread_Operation线程堵塞的时候下载线程输出的信号还再传递,所以修改为通过 回调函数 的方式传递信号,线程堵塞时都被暂停了,这样即使再使用Qt::BlockingQueuedConnection 信号连接依旧正常输出.

回调函数简单示例:
//! 定义一个回调函数
typedef std::function<void(int)> CallbackFunction_ProgressBar;
auto _T=[this](int i){
emit QThread_Down::SendMessStr(QString("\r正在下载:[%1%]...").arg(i));
};
CallbackFunction_ProgressBar _ProgressBar =_T;


方向三:bug经验教训

为什么要还要使用Qt::BlockingQueuedConnection 信号连接?
因为以前做大数据量处理时发现,多个线程同时向界面传达大量信号刷新界面,依然会造成界面卡顿,习惯性通过Qt::BlockingQueuedConnection 信号连接方式,牺牲部分数据处理速度同步界面刷新,防止界面卡顿。
而之前实际开发中,实际上默认的信号槽连接方式就已经可以了,但是因为开发习惯和不了解信号类型滥用,导致浪费大量时间处理造成的bug。
同时需要注意Qt::BlockingQueuedConnection 信号的使用不合理,可能会造成死锁!
慎用!
引以为戒阿…

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

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

相关文章

【网站推荐】the top trending open-source startups, every quarter

每季度最热门的开源初创公司 我们根据 GitHub 存储库自 2020 年以来的明星增长情况发布热门开源项目&#xff0c;并将其称为 Runa 开源初创公司 (ROSS) 指数。 una Capital actively invests in open-source startups (like Nginx and MariaDB) and considers an active deve…

java学习记录11

异常 在java中提供了处理异常的机制&#xff0c;能够帮助我们避免程序崩溃。 Throwable可以用来表示任何可以作为异常抛出的类&#xff0c;分为两种&#xff1a; Error和Exception。其中Error用来表示JVM无法处理的错误。程序被强制终止。 Exception又分为两种&#xff1a; 受…

IDEA如何导入项目,包括从git仓库(github)导入项目

前言 大家好&#xff0c;我是小徐啊。自从使用了IDEA开发Java应用后&#xff0c;我再也不想使用eclipse了。IDEA的好处真的太多了。今天小徐就来介绍下IDEA的入门知识&#xff0c;也就是如何导入一个项目。 IDEA如何导入项目 首先&#xff0c;打开IDEA&#xff0c;点击上方的…

GitLab|数据迁移

注意&#xff1a;新服务器GitLab版本需和旧版本一致 在旧服务器执行命令进行数据备份 gitlab-rake gitlab:backup:create 备份数据存储在 /var/opt/gitlab/backups/ 将备份数据传输到新服务器的/var/opt/gitlab/backups/下&#xff0c;并修改文件权限&#xff08;下载前和上传…

SSRF漏洞利用

2.漏洞利用 2.1 SSRF中URL的伪协议 file:// 从⽂件系统中获取⽂件内容&#xff0c;如&#xff0c;file:///etc/passwd dict:// 字典服务器协议&#xff0c;访问字典资源&#xff0c;如dict://ip:6379/info sftp:// ssh⽂件传输协议或安全⽂件传输协议 ldap:// 轻量级⽬录访问…

flux代码解析

https://zhuanlan.zhihu.com/p/714150390https://zhuanlan.zhihu.com/p/714150390 flux.1[pro] 1.版本 flux.1 [pro] api收费版本 flux.1 [dev] flux.1 蒸馏版本 guidance-distilled模型 flux.1 [schell] 1-4步版本 2.通读代码框

Scala之Array数组

可修改的Array import scala.collection.mutable.ArrayBuffer //Array:数组 //可修改的&#xff1a;ArrayBuffer //不可修改的&#xff1a;Array object Test1 {//可修改的&#xff1a;ArrayBufferdef main(args: Array[String]): Unit {//1.新建val arr1 ArrayBuffer(1,2,3)…

PostgreSQL常用字符串函数与示例说明

文章目录 coalesce字符串位置(position strpos)字符串长度与大小写转换去掉空格(trim ltrim rtrim)字符串连接(concat)字符串替换简单替换(replace)替换指定位置长度(overlay)正则替换(regexp_replace) 字符串匹配字符串拆分split_part(拆分数组取指定位置的值)string_to_array…

Elasticsearch 中的热点以及如何使用 AutoOps 解决它们

作者&#xff1a;来自 Elastic Sachin Frayne 探索 Elasticsearch 中的热点以及如何使用 AutoOps 解决它。 Elasticsearch 集群中出现热点的方式有很多种。有些我们可以控制&#xff0c;比如吵闹的邻居&#xff0c;有些我们控制得较差&#xff0c;比如 Elasticsearch 中的分片分…

unity3d——基础篇小项目(开始界面)

示例代码&#xff1a; using System.Collections; using System.Collections.Generic; using UnityEngine;public class BeginPanel : BasePanel<BeginPanel> {public UIButton btnBegin;public UIButton btnRank;public UIButton btnSetting;public UIButton btnQuit; …

不用手绘不用PS!如何一键生成波谱风插画?两个方法

​ 以前我们制作一张波谱风插画既要手绘又要用ps处理&#xff0c;现在我们直接用AI一键生成。接下来我用两个方法带你快速生成波谱风插画&#xff0c;一个是通过Midjourney&#xff0c;另一个是利用ComfyUI的工作流。话不多说&#xff0c;直接上干货。 波谱风插画是什么&#x…

推荐一款专业电脑护眼工具:CareUEyes Pro

CareUEyes Pro是一款非常好用的专业电脑护眼工具&#xff0c;软件小巧&#xff0c;界面简单&#xff0c;它可以自动过滤电脑屏幕的蓝光&#xff0c;让屏幕显示更加的不伤眼&#xff0c;更加舒适&#xff0c;有效保护你的眼睛&#xff0c;可以自定义调节屏幕的色调&#xff0c;从…

Ubuntu ESP32开发环境搭建

文章目录 ESP32开发环境搭建安装ESP-IDF搭建一个最小工程现象 ESP32开发环境搭建 最近有个小项目需要用到能够联网的mcu驱动&#xff0c;准备玩玩esp的芯片&#xff0c;记录下ESP32开发环境搭建的过程。 ESP-IDF 是乐鑫科技为其 ESP32 系列芯片提供的官方开发框架。这个框架主…

更改ArduSub水平位置控制器为ADRC

水平位置控制器的函数为update_xy_controller(),位于libraries/AC_AttitudeControl/AC_PosControl.cpp,现在的控制器为p-pid,p控制器将位置信息转化为速度信息,pid控制器将速度信息转化为加速度信息,然后在送给姿态控制器。 现在将当前的P控制器转化为ADRC控制器,其他的更…

ubuntu中使用ffmpeg和nginx推流rtmp视频

最近在测试ffmpeg推流rtmp视频&#xff0c;单独安装ffmpeg是无法完成推流的&#xff0c;需要一个流媒体服务器&#xff0c;常用nginx&#xff0c;可以直接在ubuntu虚拟机里面测试一下。 测试过程不涉及编译ffmpeg和nginx&#xff0c;仅使用基本功能&#xff1a; 1 安装ffmpeg …

图像处理 之 凸包和最小外围轮廓生成

“ 最小包围轮廓之美” 一起来欣赏图形之美~ 1.原始图片 男人牵着机器狗 2.轮廓提取 轮廓提取 3.最小包围轮廓 最小包围轮廓 4.凸包 凸包 5.凸包和最小包围轮廓的合照 凸包和最小包围轮廓的合照 上述图片中凸包、最小外围轮廓效果为作者实现算法生成。 图形几何之美系列&#…

【Nginx从入门到精通】05-安装部署-虚拟机不能上网简单排错

文章目录 总结1、排查步骤 一、排查&#xff1a;Vmware网关二、排查&#xff1a;ipStage 1 &#xff1a;ping 127.0.0.1Stage 2 &#xff1a;ping 宿主机ipStage 3 &#xff1a;ping 网关 失败原因解决方案Stage 4 &#xff1a;ping qq.com 总结 1、排查步骤 Vmware中网关是否…

Python Turtle召唤童年:喜羊羊与灰太狼之懒羊羊绘画

Python Turtle召唤童年&#xff1a;喜羊羊与灰太狼之懒羊羊绘画 &#x1f438; 前言 &#x1f438;&#x1f41e;往期绘画&#x1f41e;&#x1f40b; 效果图 &#x1f40b;&#x1f409; 代码 &#x1f409; &#x1f438; 前言 &#x1f438; 小时候&#xff0c;每次打开电视…

机器学习问题之一:协变量偏移(Covariate Shift)

协变量偏移&#xff08;Covariate Shift&#xff09;是机器学习和深度学习中的一个重要概念&#xff0c;指的是在模型训练和应用时&#xff0c;输入数据&#xff08;特征&#xff09;的分布发生了变化&#xff0c;但输出标签的分布保持不变。这会导致模型在训练集上表现良好&am…

【UGUI】Unity 背包系统实现02:道具信息提示与显示

在游戏开发中&#xff0c;背包系统是一个常见的功能模块&#xff0c;用于管理玩家拾取的物品。本文将详细介绍如何在 Unity 中实现一个简单的背包系统&#xff0c;包括道具信息的提示和显示功能。我们将通过代码和场景搭建来逐步实现这一功能。 1. 功能需求清单 在实现背包系…