Qt事件循环嵌套,BlockingQueuedConnection与QWaitCondition比较

news2024/11/19 3:23:01

前言:

之前写过有关事件循环和条件变量的博客:

Qt使用事件循环,信号,stop变量,sleep阻塞,QWaitCondition+QMutex条件变量,退出子线程工作_大橘的博客-CSDN博客_qt stop函数

Qt事件循环(QCoreApplication::processEvents,exec)的应用_大橘的博客-CSDN博客_qcoreapplication::processevents()

主要用于线程间通信,实现子线程控制。

偶然看到一位朋友的博客:

14.QueuedConnection和BlockingQueuedConnection连接方式源码分析_Master Cui的博客-CSDN博客_blockingqueuedconnection

他很了不起,写了很多有深度的文章。从这篇博客中,原本很常用的信号槽机制,我突然想研究一下阻塞队列连接方式的效果。亦即:Qt::BlockingQueuedConnection,connect函数的最后一个参数。

信号槽连接方式:

qt信号槽的连接方式一共就这几种:

Qt::AutoConnection

Qt::DirectConnection

Qt::QueuedConnection

Qt::BlockingQueuedConnection

Qt::UniqueConnection

qt手册以及网络上的介绍太多太详细了。其实很简单,通常我不用这个参数,直接就是auto模式,qt会自动布置。

信号与槽在同一个线程时,默认是direct方式,也就是顺序执行的,发送信号以后立刻执行槽函数,然后执行发信号后面的语句。发信号就好像调用函数一样,执行完函数再执行后面的代码。

信号与槽不再同一个线程时,默认是队列模式,执行顺序受os调度影响,异步执行。发送信号以后不会立刻执行另一个线程的槽函数,而是把发送信号后面的语句一口气执行完,再执行另一个线程的槽。这里涉及事件循环的概念,不再赘述。

同样是信号与槽不再一个线程,还可以采用阻塞队列模式,就是同时具备上两种方式的特点。发送完信号,直接执行槽,然后执行发信号后面的代码。发信号这个线程会阻塞一下,等待槽执行完毕。

最后一种见名知意,不允许多次连接,如果不选这种模式,连接一次,就会触发一次槽。

原本上面这些连接方式不需要特别深入,知道即可。但是我对阻塞队列模式感兴趣,因为它特别像条件变量的效果,QWaitCondition。所以,先看一种场景,事件循环嵌套。

事件循环嵌套:

先看一段简单的代码:

#include "obj.h"
#include <QThread>
#include <QCoreApplication>
#include <QDebug>

Obj::Obj()
{

}

void Obj::f_Start()
{
    m_bStop = false;
    m_iLoopLevel++;

    while (true)
    {
        QCoreApplication::processEvents();//事件循环

        if (m_bStop)//退出判断
        {
            break;
        }

        QThread::msleep(200);
    }

    qDebug() << "Sub thread: event loop exists level: " << m_iLoopLevel;
    m_iLoopLevel--;

    if (m_iLoopLevel == 0)
    {
        f_Caller_Wakeup();
        qDebug() << "Sub thread: end.";
    }
}

void Obj::onStart()//这是一个槽函数,如果多次被触发,会让上面while循环中的eventloop嵌套
{
    f_Start();
}

void Obj::onStop()//用于接受停止命令,从而退出while循环,结束子线程
{
    qDebug() << "Sub thread: stopping...";
    m_bStop = true;
}

同时我做了一个窗体,用于人为控制子线程。

 点一次start就触发一层事件循环,点击stop会终止子线程所有循环。

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "obj.h"
#include <QThread>
#include <QDebug>
#include <QTextCodec>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    m_obj = new Obj;
    connect(this, SIGNAL(sigStart()), m_obj, SLOT(onStart()));
    connect(this, SIGNAL(sigStop()), m_obj, SLOT(onStop()));
    m_obj->setParent(nullptr);

    QThread *thd = new QThread;
    thd->start();

    m_obj->moveToThread(thd);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_btnStart_clicked()
{
    emit sigStart();
}

void MainWindow::on_btnStop_clicked()
{
    qDebug() << "Main thread send signal";
    emit sigStop();
    qDebug() << "Main thread end.";
}

期望场景:

现在开始玩上面的代码,先说理想情况,我希望多次点击start产生循环嵌套以后,点击stop时,子线程逐级退出循环,而且主线程能正确获得已经结束的时机,从而再执行清理代码的时候不会造成野循环。

按照上面代码输出debug文本,我希望是这个顺序:

Main thread send signal.//主线程发信号
Sub thread: stopping...//子线程开始自我终结
Sub thread: event loop exists level:*//可能会有多级
Sub thread: end.//子线程结束
Main thread end.//主线程结束

上面只是理想,如果就按照上面的代码执行,效果不会实现,槽的执行方式默认时队列方式,它会先把主线程执行完,再执行子线程,这样在主线程写清理代码的时机就很重要,如果操作不当,会造成子线程野循环,报错。

Main thread send signal.//主线程发信号
Main thread end.//主线程结束
Sub thread: stopping...//子线程开始终结
Sub thread: event loop exists level:  3
Sub thread: event loop exists level:  2
Sub thread: event loop exists level:  1
Sub thread: end.//子线程结束

就如上面这样,显然不是想要的。如果一定要这样用,在子线程结束时还要给主线程发信号来触发清理操作。

使用阻塞连接方式:

按照阻塞连接的方式改一下主线程代码:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "obj.h"
#include <QThread>
#include <QDebug>
#include <QTextCodec>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    m_obj = new Obj;
    connect(this, SIGNAL(sigStart()), m_obj, SLOT(onStart()));

    //使用阻塞连接方式,就改这一个地方,只是加了一个参数
    connect(this, SIGNAL(sigStop()), m_obj, SLOT(onStop()), Qt::BlockingQueuedConnection);

    m_obj->setParent(nullptr);

    QThread *thd = new QThread;
    thd->start();

    m_obj->moveToThread(thd);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_btnStart_clicked()
{
    emit sigStart();
}

void MainWindow::on_btnStop_clicked()
{
    qDebug() << "Main thread send signal.";
    emit sigStop();
    qDebug() << "Main thread end.";
}

只在connect的时候加了一个参数而已,再看效果。

Main thread send signal.//主线程发信号
Sub thread: stopping...//子线程执行槽,执行完槽回到eventloop
Main thread end.//主线程继续结束
Sub thread: event loop exists level:  3//子线程退出逐级循环
Sub thread: event loop exists level:  2
Sub thread: event loop exists level:  1
Sub thread: end.//子线程结束

看效果有点那个意思,但是子线程一旦执行完槽函数,它就回到自己最近的一层事件循环,这就算处理完消息队列了,所以接着会执行主线程的代码。因此还无法实现最初的想法。所以就有了使用条件变量的方法。

使用条件变量:

子线程对象定义,加入条件变量和响应的锁:

#ifndef OBJ_H
#define OBJ_H

#include <QObject>
#include <QWaitCondition>
#include <QMutex>

class Obj : public QObject
{
    Q_OBJECT
public:
    Obj();
    QWaitCondition      m_condition;//条件变量
    QMutex              m_mutex;//互斥锁

    void f_Caller_Wait();//用于主线程阻塞
    void f_Caller_Wakeup();//用于主线程唤醒

private slots:
    void onStart();
    void onStop();

private:
    bool m_bStop = true;
    int m_iLoopLevel = 0;
    void f_Start();
};

#endif // OBJ_H

子线程实现:

#include "obj.h"
#include <QThread>
#include <QCoreApplication>
#include <QDebug>

Obj::Obj()
{

}

void Obj::f_Start()
{
    m_bStop = false;
    m_iLoopLevel++;

    while (true)
    {
        QCoreApplication::processEvents();

        if (m_bStop)
        {
            break;
        }

        QThread::msleep(200);
    }

    qDebug() << "Sub thread: event loop exists level: " << m_iLoopLevel;
    m_iLoopLevel--;

    if (m_iLoopLevel == 0)
    {
        f_Caller_Wakeup();//退出最后一层循环后,唤醒主线程
        qDebug() << "Sub thread: end.";
    }
}

void Obj::onStart()
{
    f_Start();
}

void Obj::onStop()
{
    qDebug() << "Sub thread: stopping...";
    m_bStop = true;
}

void Obj::f_Caller_Wait()
{
    qDebug() << "Main thread wait...";
    m_mutex.lock();
    m_condition.wait(&m_mutex);
    m_mutex.unlock();
}

void Obj::f_Caller_Wakeup()
{
    qDebug() << "Main thread continue.";
    m_mutex.lock();
    m_condition.wakeAll();
    m_mutex.unlock();
}

主线程实现:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "obj.h"
#include <QThread>
#include <QDebug>
#include <QTextCodec>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    m_obj = new Obj;
    connect(this, SIGNAL(sigStart()), m_obj, SLOT(onStart()));
    connect(this, SIGNAL(sigStop()), m_obj, SLOT(onStop()));//使用默认连接方式
    m_obj->setParent(nullptr);

    QThread *thd = new QThread;
    thd->start();

    m_obj->moveToThread(thd);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_btnStart_clicked()
{
    emit sigStart();
}

void MainWindow::on_btnStop_clicked()
{
    qDebug() << "Main thread send signal.";
    emit sigStop();
    m_obj->f_Caller_Wait();//主线程等待
    qDebug() << "Main thread end.";
}

执行效果如下:

Main thread send signal.//主线程发信号
Main thread wait...//主线程等待
Sub thread: stopping...//子线程开始终结
Sub thread: event loop exists level:  6
Sub thread: event loop exists level:  5
Sub thread: event loop exists level:  4
Sub thread: event loop exists level:  3
Sub thread: event loop exists level:  2
Sub thread: event loop exists level:  1
Main thread continue.//唤醒主线程
Sub thread: end.//子线程终结
Main thread end.//主线程终结

这就实现最初的想法了。

意义:

之所以要这样做,我是希望主线程能够在终结子线程操作的时候能够保持时序,终结子线程行为之后,可以马上执行清理操作,而不会报错。

如果不这样,发完信号就清理,子线程还没来得及停止所有操作,就已经被释放内存,内存倒是没泄露,可是正在执行的循环没有跳出,就是野循环,早晚会耗尽资源卡死,甚至直接报错。

当然也可以主线程发完信号之后不要马上清理,子线程完成后发信号通知主线程已经结束,再清理。下面试试这种方式。

子线程回复方式:

子线程加一个sigStopped信号,发给主线程。主要看一下实现代码。

子线程实现:

#include "obj.h"
#include <QThread>
#include <QCoreApplication>
#include <QDebug>

Obj::Obj()
{

}

void Obj::f_Start()
{
    m_bStop = false;
    m_iLoopLevel++;

    while (true)
    {
        QCoreApplication::processEvents();

        if (m_bStop)
        {
            break;
        }

        QThread::msleep(200);
    }

    qDebug() << "Sub thread: event loop exists level: " << m_iLoopLevel;
    m_iLoopLevel--;

    if (m_iLoopLevel == 0)
    {
        qDebug() << "Sub thread: end.";
        emit sigStopped();//退出最后一层循环后,通知主线程
    }
}

void Obj::onStart()
{
    f_Start();
}

void Obj::onStop()
{
    qDebug() << "Sub thread: stopping...";
    m_bStop = true;
}

主线程实现:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "obj.h"
#include <QThread>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    m_obj = new Obj;
    connect(this, SIGNAL(sigStart()), m_obj, SLOT(onStart()));
    connect(this, SIGNAL(sigStop()), m_obj, SLOT(onStop()));
    connect(m_obj, SIGNAL(sigStopped()), this, SLOT(onSubStopped()));//增加一个回复连接
    m_obj->setParent(nullptr);

    QThread *thd = new QThread;
    thd->start();

    m_obj->moveToThread(thd);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_btnStart_clicked()
{
    emit sigStart();
}

void MainWindow::on_btnStop_clicked()
{
    qDebug() << "Main thread send signal.";
    emit sigStop();//发送完信号什么也不做,等待子线程回复
}

void MainWindow::onSubStopped()//子线程发回已终止信号之后被触发
{
    qDebug() << "Main thread end.";
}

执行效果:

Main thread send signal.//主线程发信号
Sub thread: stopping...//子线程开始自我终结
Sub thread: event loop exists level:  3
Sub thread: event loop exists level:  2
Sub thread: event loop exists level:  1
Sub thread: end.//子线程终止
Main thread end.//主线程终止

看效果还是不错的。

按说最后一种异步方式更灵活,但是看情况,太多的异步交互不太容易阅读代码。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity3D导出Android工程中使用并交互

, 目录 1&#xff0c;版本信息 2&#xff0c;前期准备 Unity方面&#xff1a; Android方面&#xff1a; 3&#xff0c;Android与Unity3D交互 1&#xff0c;版本信息 unity2020 android studio 2021 *不要用android studio 2020系列&#xff0c;存在不能导入Library的b…

Spring学习 | Bean作用域生命周期

文章目录一、作用域1.1 xml文件中配置1.2 注解配置二、生命周期2.1 四个阶段2.2 添加后置处理器2.3 实现aware类型接口2.4 Bean 初始化的方式2.5 Bean 销毁的方式2.6 测试程序学习视频&#x1f3a5;&#xff1a;https://www.bilibili.com/video/BV1Vf4y127N5 一、作用域 ❓ 引入…

Linux系统中裸机按键中断的驱动方法

大家好&#xff0c;今天主要和大家聊一聊&#xff0c;如何实现按键中断的驱动​方法。 目录 ​第一&#xff1a;外部中断头文件实现 ​第二&#xff1a;外部中断源文件的具体实现 ​第三&#xff1a;编写对应的main.c函数 ​第一&#xff1a;外部中断头文件实现 #ifndef _…

基于keras平台CNN神经网络模型的服装识别分析

在许多介绍图像识别任务的介绍中&#xff0c;通常使用着名的MNIST数据集。 最近我们被客户要求撰写关于图像识别的研究报告&#xff0c;包括一些图形和统计输出。但是&#xff0c;这些数据存在一些问题&#xff1a; 1.太简单了。例如&#xff0c;一个简单的MLP模型可以达到99…

Java搭建宝塔部署实战毕设项目springboot客户管理系统源码

大家好啊&#xff0c;我是测评君&#xff0c;欢迎来到web测评。 本期给大家带来一套Java开发的毕业设计项目&#xff0c;springboot客户管理系统源码&#xff0c;感兴趣的朋友可以自行下载搭建测试。 技术架构 技术框架&#xff1a;SpringBoot MySQL5.7 mybatis shiro Lay…

DDR4时序标准规范(一)

DDR4时序标准规范引脚描述DDR4 SDRAM寻址DDR4架构的模块描述功能描述简化状态机基本功能复位和初始化程序上电和初始化顺序电压稳定后的复位初始化顺序无控制的下电顺序引脚描述 标志类型功能CK_t, CK_c输入Clock: CK_t和CK_c是差分时钟输入。所有的地址和控制输入信号在CK_t的…

主成分分析(PCA)原理及R语言实现及分析实例

主成分分析&#xff08;PCA&#xff09;是一种数据降维技巧&#xff0c;它能将大量相关变量转化为一组很少的不相关变量&#xff0c;这些无关变量称为主成分。最近我们被客户要求撰写关于主成分分析&#xff08;PCA&#xff09;的研究报告&#xff0c;包括一些图形和统计输出。…

Vagrant搭建Centos

1.下载安装vagrant 01访问Vagrant官网 ​ https://www.vagrantup.com/ 02 点击Download ​ Windows&#xff0c;MacOS&#xff0c;Linux等 03 选择对应的版本 04 傻瓜式安装 05 命令行输入vagrant&#xff0c;测试是否安装成功,显示如下&#xff1a; 2.下载安装virtua…

半解析快速傅里叶变换

我们提出了一种处理傅里叶变换的方法&#xff0c;其并不需要二次多项式相位项的抽样&#xff0c;而是用解析的方法处理。我们提出该理论的同时也给出了几个例子证明其潜力。 1.简介 物理光学建模需要频繁地从空间转换到角频域&#xff0c;反之亦然。这可以由电场和磁场分…

网络编程(用于不同电脑之间的信息交互):UDP、TCP

网络编程&#xff1a; 在网络通信写一下&#xff0c;不同计算机运行的程序&#xff0c;可以进行数据传输 IP地址&#xff1a;设备&#xff08;手机、电脑等&#xff09;在网络中的地址&#xff0c;是唯一的标识 端口&#xff1a;应用程序在设备中唯一的表示 协议&#xff1a…

数据结构学习——表、查找

定义 设记录表L(R1 R2…其中Ri(L<i<n)为记录&#xff0c; 对给定的某个值k&#xff0c; 在表L中确定key k的记录的过程&#xff0c;称为查找。若表Lz中存在记录Ri de key k,记为Ri.key,则查找成功&#xff0c;返回该记录在表L中的序号i&#xff08;或Ri的地址&#xff…

优秀的 Verilog/FPGA开源项目介绍(二十)- 张量处理单元(TPU)

介绍张量处理单元( Tensor Processing Unit, TPU ) 是谷歌专门为神经网络机器学习开发的人工智能加速器 专用集成电路(ASIC) &#xff0c;特别是使用谷歌自己的TensorFlow软件。谷歌于 2015 年开始在内部使用 TPU&#xff0c;并于 2018 年将它们作为其云基础设施的一部分并通过…

Zabbix6.0使用教程 (三)—zabbix6.0的安装要求

接上篇&#xff0c;我们继续为大家详细介绍zabbix6.0的使用教程之zabbix6.0的安装部署。接下来我们将从zabbix部署要求到四种不同的安装方式逐一详细的为大家介绍。本篇讲的是部署zabbix6.0的要求。 zabbix6.0安装要求 硬件&#xff1a;内存和磁盘 Zabbix6.0安装 运行需要物…

算法精品讲解(2)——DP问题入门(适合零基础者,一看就会)

目录 前言 DP问题它是什么&#xff08;了解&#xff09; 从中学的例题谈起 再来说一下&#xff0c;DP问题的核心思想&#xff08;理解&#xff09; DP问题的解决方法 先说方法论&#xff1a; 再说具体的例子 例一&#xff1a; 例二&#xff1a; 例三&#xff1a; DP和…

kotlin之range范围表达式

Kotlin 中的 Range 有 CharRange、LongRange、IntRange range 范围 CharRange、LongRange、IntRange 范围区间 var a:IntRange 50..100for (i in a){ //遍历50~100的分数分别在什么位置print("成绩&#xff1a;$i")if(i in 1..59){ //1~59 范围println("…

Leica Infinity三维映射环境数据

Leica Infinity三维映射环境数据 Leica Infinity是软件工程师和该领域专家的名字&#xff0c;以及您的工作计划信息。该软件被设计和呈现为一个强大的产品&#xff0c;并且来自六边形组。Leica Infinity产品的居民试图用新的眼光创造新的数据处理。使用此软件&#xff0c;您可以…

和ChatGPT 比一比谁更懂Kubernetes?

有时&#xff0c;很难得到关于云原生世界中棘手话题的明确答案。哪个是最好的服务网格&#xff1f;平台工程只是devops的另一个标签吗&#xff1f;多云是一种风险吗&#xff1f; 如果你无法从一个人那里得到直截了当的答案——为什么不问一台机器呢&#xff1f; 因此&#xf…

net6自动注册到Consul 代码实例

简单理解: 服务多的时候&#xff0c;服务地址都是写固定&#xff0c;增加一个地址&#xff0c;配置一次&#xff0c;配置nginx或者其他配置&#xff0c;麻烦 有了这个就可以通过应用服务上报服务名servicename和访问地址&#xff0c;同一个服务名servicename可以有多个节点&a…

【教学类-15-05】20221207《八款字体的描字帖-2*4格》(中班大班)

成品样式&#xff1a; 80号字&#xff08;适应2-3个名字的大小&#xff09; 68号字&#xff08;适应4个名字大小&#xff08;2-3个名字也可以用&#xff0c;字会很小&#xff09;&#xff09; 打印样式&#xff1a; 背景需求&#xff1a; 前期进行多次的Python学具教学活动&a…

能跟CAD、BIM软件联合使用的地图神器,比奥维谷歌地图还方便!

是的&#xff0c;今天王工推荐的跟之前的地图工具不一样&#xff0c;百度、谷歌、MAPBOX那些称之为地图工具&#xff0c;这个简直就是地图的浏览器&#xff01;而且它还可以与CAD、BIM软件联合使用&#xff01; 实在是比奥维地图“香”。 海量图源任意浏览 ▲ 任意切换各种地…