深度解析qt核心机制:信号槽的多线程行为与对象的线程依附性

news/2024/7/1 22:17:17 标签: qt, 开发语言

对象的线程依附性

每一个学过C++以及系统编程的程序员,对于变量会与特定线程有关联都会感到不可思议;在qt中所说的对象的线程依附性,只是针对继承自QObject的对象而言的;对象的线程依附性,并不是代表真的某个底层线程才能访问这个变量而其他线程不行;而是一种qt实现逻辑上的标记需要;这个qt实现逻辑就是qt核心机制信号槽机制;
qt对象的线程依附性的真正含义是:这个对象只接收或者只处理所依附线程的事件队列里面的事件【有人会问这跟信号槽有什么关系?请先记住这句话!】

qt中每一个线程都可以有一个唯一的事件队列【类似于windows里面的消息队列】,线程事件队列中接受存放过来的事件任务,这个线程也进行事件循环从事件队列中取出事件任务分派给对应的对象去处理【类似于消息循环分派消息给对应的窗口处理,但是qt中这时分派给对象处理】;注意这里分派给继承自QObject的对象处理;对象所处理的事件任务,一定是从对象所依附的线程的事件队列中取出的任务!

我们现在已经讲了 线程事件队列,线程事件循环,对象的线程依附性;现在来看看connect也就是信号槽的真正语义是什么;
无论采用何种策略,connect的主体语义只有二种
1.同一线程内直接调用:这时信号的触发或者说调用信号线程与槽函数的触发执行是同一线程;【无论这个emit是手动显示调用还是预定义信号底层通过消息事件触发的】对应的emit的语义就是单线程内的直接调用
2.不同线程间的一个线程存放事件任务到另一个线程的事件队列中:这时信号的触发(调用信号)的线程就是存放动作的发出者,由这个线程存放事件任务到接收者所依附线程的事件队列中;所以这时候emit的语义就是事件任务存放到事件队列!

这里有几个需要注意说明的点:
1.信号触发线程,或者是信号调用线程指的是执行(调用)emit【无论是显示还是隐式】的线程,而非connect 发送者对象所依附的线程!
2.接收者依附线程确实指的是接收者对象所依附的线程

一般而言对象所依附的线程是创建这个对象时【即调用这个对象的构造函数】所在的线程!后面这个对象可以被moveToThread依附到其他线程,但是执行这个操作时需要注意,调用执行这个moveToThread的线程必须是此时这个对象所依附的线程【即依附线程本身才有权决定转让依附权给其他线程】

关于QThread对象的管理线程与所依附线程关系:
QThread对象的管理线程与所依附的线程不是一个线程;QThread对象管理的线程是一个新的底层线程,该线程被QThread对象管理【比如在QThread对象生命周期结束时,必须等待期管理的线程先结束】;
而QThread对象所依附的线程,是定义(创建)QThread对象的线程,可能是GUI线程也可能是其他线程;

connect链接类型参数
Qt::AutoConnection 如果发送信号所在的线程与接受者所依附的线程是同一个线程就是Qt::DirectConnection策略;否则就是Qt::QueuedConnection策略;注【这里所说的发送信号所在的线程是指触发调用 emit 信号的执行线程,并不一定是发送者所依附的线程!】

Qt::DirectConnection 同一线程情况下才会触发此命令;直接立即在同一线程内调用槽函数代码段;发送端此时会被阻塞等待立即调用的完成;原理:最简单的理解成把一段代码“临时插入”到了运行栈;【需要注意可重入性问题】
【注:若信号调用线程与接受者依附线程是不同的线程,但是connect链接强制指定了direct模式,槽函数的执行线程依然是在信号调用线程上,这意味着信号调用的地方会等待槽函数执行结束返回;如果非要谈此时接收者所依附的线程本身处于什么状态,我只能说处于处理事件循环,或者阻塞待处理事件循环的状态】

Qt::QueuedConnection 发送端与接受者所属线程不一样;存放事件到接收者所依附的线程,发送端不阻塞,继续往下执行;接收者等待所属线程的事件循环处理到此派发任务;【若发送端和接受者依附线程一样,强制使用Qt::QueuedConnection方式连接=>这其实是一种延迟行为信号发送线程发送完后继续往下执行,这时槽函数还没被执行,一直到调用信号发送的位置执行完后进入事件循环,处理到刚刚加入的事件后才执行槽函数(处理需要延迟的任务时候用)】

Qt::BlockingQueuedConnection 发送端与接受者所属线程不一样;存放事件到接收者所依附的线程,发送端阻塞等待接收者获得分派的事件任务处理完成后再执行;如果发送端线程与接受者所属线程一样;势必造成死锁行为;

Qt::UniqueConnection 独占链接;多个相同链接调用只成功一个;【相同判定:发送者-信号,接受者-槽都对应相同】
Qt::SingleShotConnection 一次性链接,触发一次槽调用后,这段链接会自动断开;

关于connect链接类型的一些注意事项:
1.Qt::UniqueConnection:当未使用Qt::UniqueConnection指定连接时,多次使用connect( )对同一信号槽建立连接时,这个信号会被触发多次。可以使用Qt::UniqueConnection指定只建立一次连接,这样该信号不会被触发多次,但是Qt::UniqueConnection只对成员函数起作用,不能将它使用到非成员函数的槽以及lambda表达式等。

2.Qt::QueuedConnection:该连接类型只适用于元对象类型。在使用该类型连接之前确保所连接的对象已经注册了元类型以及传递的参数注册了元类型,因为Qt的事件系统需要知道该类型信息,若没有注册元类型该连接不会建立,可以使用qRegisterMetaType()或者Q_DECLARE_METATYPE宏进行元类型注册。

关于信号槽同一个信号链接多个槽函数的执行顺序的新标准(qt5.0之后):
所有这些链接被触发时的最终判定【即根据发送信号所在线程,接收者依附线程,以及链接策略;判定应该在哪个线程上执行槽函数】的结果;被分配在同一个线程上执行的槽函数之间的执行顺序与其connect链接的声明顺序一致;分配在不同线程上执行的槽函数之间执行的顺序不确定!

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "mythread.h"
#include<QThreadPool>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    ui->widget_2->setWindowTitle("222");
    ui->widget_2->show();//无效
    ui->textEdit->setText("112");//贯穿widget容器

    // 1. 创建任务对象
    Generate* gen = new Generate(this);
    BubbleSort* bubble = new BubbleSort(this);
    QuickSort* quick = new QuickSort(this);

    //设置线程池线程数量
    QThreadPool::globalInstance()->setMaxThreadCount(3);

    connect(this, &MainWindow::starting, gen, &Generate::recvNum);
    // 2. 启动子线程
    //ui->start的clicked信号是GUI线程调用的,this依附的线程也是GUI线程,所以 emit starting调用是在GUI线程执行的
    connect(ui->start, &QPushButton::clicked, this, [=]()
    {
        emit starting(10000);//因为这个是在GUI线程执行,而gen的所依附线程也是GUI线程,所以这里是在GUI线程直接调用&Generate::recvNum,再调用下面的,故这里也不会出现数据竞争
        QThreadPool::globalInstance()->start(gen);//将gen放入任务队列,待空闲线程取用
    });
    //一个信号链接多个槽,&Generate::sendArray的调用肯定是在另一个线程,而bubble,quick,this对象依附线程是GUI线程,所以这里三个槽函数是会在同一个线程内触发,qt新标准规定这种触发顺序与connect顺序一致
    connect(gen, &Generate::sendArray, bubble, &BubbleSort::recvArray);
    connect(gen, &Generate::sendArray, quick, &QuickSort::recvArray);
    // 接收子线程发送的数据
    connect(gen, &Generate::sendArray, this, [=](QVector<int> list){
        //所以这里上面的recvArray已经触发,甚至是在同一个GUI线程中触发完毕的,这里也不会有数据竞争
        QThreadPool::globalInstance()->start(bubble);
        QThreadPool::globalInstance()->start(quick);
        for(int i=0; i<list.size(); ++i)
        {
            ui->randList->addItem(QString::number(list.at(i)));
        }
    });
    connect(bubble, &BubbleSort::finish, this, [=](QVector<int> list){
        for(int i=0; i<list.size(); ++i)
        {
            ui->bubbleList->addItem(QString::number(list.at(i)));
        }
    });
    connect(quick, &QuickSort::finish, this, [=](QVector<int> list){
        for(int i=0; i<list.size(); ++i)
        {
            ui->quickList->addItem(QString::number(list.at(i)));
        }
    });

    //因为现在gen对象其实是一个task对象而非线程对象;所以gen不需要管理线程,线程由线程池管理;
    //并且 task任务对象设置了setAutoDelete(true);这会在每个任务对象的run方法执行完后自动的去释放task对象;所以也不需要手动delete
//    connect(this, &MainWindow::destroy, this, [=]()
//    {
//        gen->quit();
//        gen->wait();
//        gen->deleteLater();  // 等价与 delete gen;

//        bubble->quit();
//        bubble->wait();
//        bubble->deleteLater();

//        quick->quit();
//        quick->wait();
//        quick->deleteLater();
//    });
}

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



http://www.niftyadmin.cn/n/5315165.html

相关文章

git克隆失败提示RPC failed的解决方法

现象 $ git clone https://github.com/guillemj/dpkg.git Cloning into dpkg... remote: Enumerating objects: 113312, done. remote: Counting objects: 100% (18045/18045), done. remote: Compressing objects: 100% (3915/3915), done. error: RPC failed; curl 18 trans…

【现代密码学】笔记3.1-3.3 --规约证明、伪随机性《introduction to modern cryphtography》

【现代密码学】笔记3.1-3.3 --规约证明、伪随机性《introduction to modern cryphtography》 写在最前面私钥加密与伪随机性 第一部分密码学的计算方法论计算安全加密的定义&#xff1a;对称加密算法 伪随机性伪随机生成器&#xff08;PRG&#xff09; 规约法规约证明 构造安全…

强化学习的数学原理学习笔记 - 值函数近似(Value Function Approximation)

文章目录 概览&#xff1a;RL方法分类值函数近似&#xff08;Value function approximation&#xff09;Basic idea目标函数&#xff08;objective function&#xff09;优化算法&#xff08;optimization algorithm&#xff09; Sarsa / Q-learning with function approximati…

Github 2024-01-06 开源项目日报Top10

根据Github Trendings的统计&#xff0c;今日(2024-01-06统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目3Java项目1TypeScript项目1HTML项目1Go项目1HCL项目1Rust项目1Jupyter Notebook项目1非开发语言项目…

基于pytorch的房价预测

简介 本文主要介绍的基于pytorch和房价预测深度学习网络构建。 该系统使用的是网络上的开源数据&#xff1a; 实现了对房价数据的处理&#xff0c;包括词频统计、情感分析等&#xff0c;并将分析结果以图表形式进行展示。通过这个系统&#xff0c;用户可以便捷地进行分析和可…

将Django项目从本地上传至宝塔服务器(踩坑记录)

文章目录 写在前面配置本地文件配置宝塔面板解决遇到问题展示运行结果热门文章 自我介绍 ⭐2022年度CSDN 社区之星 Top6 ⭐2023年度CSDN 博客之星 Top16 ⭐2023年度CSDN 城市之星 Top2&#xff08;苏州&#xff09; ⭐CSDN Python领域 优质创作者 ⭐CSDN 内容合伙人 推荐热门…

facebook广告的内容应该怎么写

在编写Facebook广告时&#xff0c;需要注意以下几个方面&#xff1a; 明确目标&#xff1a;在创建广告之前&#xff0c;你需要明确你的目标。你是想要吸引更多的潜在客户&#xff0c;还是提高品牌知名度&#xff1f;或者是增加销售额&#xff1f;你的目标将决定广告的文案和视…

计算机网络——应用层(2)

计算机网络——应用层&#xff08;2&#xff09; 小程一言专栏链接: [link](http://t.csdnimg.cn/ZUTXU) Web和HTTP概念解读HTTPHTTP请求和响应包含内容常见的请求方法Web缓存优点缺点 总结 DNS提供的服务 小程一言 我的计算机网络专栏&#xff0c;是自己在计算机网络学习过程…