您的位置:首页 > 财经 > 产业 > 全国今日疫情最新消息_国际欧美专线_互联网推广项目_网站建设的流程是什么

全国今日疫情最新消息_国际欧美专线_互联网推广项目_网站建设的流程是什么

2024/12/22 15:50:17 来源:https://blog.csdn.net/qq_35554617/article/details/143934248  浏览:    关键词:全国今日疫情最新消息_国际欧美专线_互联网推广项目_网站建设的流程是什么
全国今日疫情最新消息_国际欧美专线_互联网推广项目_网站建设的流程是什么

记录项目开发过程中,使用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);//! 修改操作1void Function1();//! 修改操作2void 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("开始下载...");elseemit 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()<<"线程启动!";elseqDebug()<<"线程结束!";});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("开始下载...");elseemit 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 信号连接依旧正常输出.

使用回调函数解决死锁问题

在没有判断出 是Qt::BlockingQueuedConnection 连接方式造成的问题之前,我是通过定义回调函数的方法解决的这个问题,
这里做一个Qt开发中常使用的回调函数的调用示例:

回调函数简单示例:
//! 定义一个回调函数
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;

实际操作:

  • 重写下载类 Dal_DownData

使用std::function 定义回调函数类型,
在传递信号的地方调用回调函数。
定义回调函数类型变量,伪代码。
Dal_DownData.h

#include <QObject>
#include <iostream>
#include <functional>
#include <thread>
#include <chrono>#include <Windows.h>
//! 定义一个回调函数
typedef std::function<void(int)>     CallbackFunction_ProgressBar;
typedef std::function<void(QString)> CallbackFunction_StyleStr;
typedef std::function<void(bool)>    CallbackFunction_IsStart;//! 定义一个下载类 伪代码
class Dal_DownData:public QObject
{Q_OBJECT
public:Dal_DownData();void StartDown(QString file="text");//! 设置信号的回调函数void SetCallBack(CallbackFunction_ProgressBar _ProgressBar=nullptr,CallbackFunction_StyleStr    _StyleStr=nullptr,CallbackFunction_IsStart     _IsStart=nullptr){PROGRESSBAR = _ProgressBar;STYLESTR    = _StyleStr;ISSTART     = _IsStart;}
Q_SIGNALS:///开始下载void IsStart(bool bol);/// 直接进度条设置业务值(百分值)void ProgressBar(int value);/// 直接进度条设置业务值(百分值)void StyleStr(QString type);
private:CallbackFunction_ProgressBar PROGRESSBAR=nullptr;CallbackFunction_StyleStr    STYLESTR=nullptr;CallbackFunction_IsStart     ISSTART=nullptr;
};

Dal_DownData.cpp

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

修改在线程类中下载类的绑定方式;
其中可以通过std::bindlambda 表达式绑定回调函数,通过方法赋值变量。如下示例:

    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));
//    });//! 绑定回调函数两种方法// 使用 std::bind 将 QThread_Down 的成员函数绑定到其实例上auto _DownStyleStr = std::bind(&QThread_Down::DownStyleStr, this,std::placeholders::_1);// 使用 lambda 表达式捕获 QThread_Down 的实例并调用其成员函数auto _IsStart=[this](bool bol){if(bol)emit QThread_Down::SendMessStr("开始下载...");elseemit QThread_Down::SendMessStr("下载结束...");};auto _ProgressBar=[&](int val){emit QThread_Down::SendMessStr(QString("\r下载进度:[%1%]...").arg(val));};Down->SetCallBack(_ProgressBar,_DownStyleStr,_IsStart);

使用回调函数后,在使用Qt::BlockingQueuedConnection 连接也不会造死锁问题了…


方向三:bug经验教训

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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com