深度解析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.mzph.cn/news/611380.shtml

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

相关文章

MS-DETR论文解读

文章目录 前言一、摘要二、引言三、贡献四、MS-DETR模型方法1、模型整体结构解读2、模型改善结构解读3、一对多监督原理 五、实验结果1、实验比较2、论文链接 总结 前言 今天&#xff0c;偶然看到MS-DETR论文&#xff0c;以为又有什么高逼格论文诞生了。于是&#xff0c;我想查…

PandoraNext—一个让你呼吸顺畅的ChatGPT

博客地址 PandoraNext—一个让你呼吸顺畅的ChatGPT-雪饼 (xue6ing.cn)https://xue6ing.cn/archives/pandora--yi-ge-rang-ni-hu-xi-shun-chang-de-chatgpt 项目 项目地址 pandora-next/deploy 项目介绍 支持多种登录方式&#xff1a; 账号/密码 Access Token Session To…

【大数据OLAP引擎】StartRocks存算分离

存算分离的原因 降低存储成本&#xff1a;同样的存储大小对象存储价格只有SSD的1/10&#xff0c;所以号称存储成本降低80%不是吹的。 存算一体到存算分离 存算一体 作为 MPP 数据库的典型代表&#xff0c;StarRocks 3.0 版本之前使用存算一体 (shared-nothing) 架构&#xf…

关于Spring源码学习 这里是一些建议

学习Spring源码的过程可以分为以下几个步骤&#xff1a; 准备工具和环境 首先&#xff0c;你需要安装并配置一个合适的IDE&#xff0c;如IntelliJ IDEA或Eclipse。这些IDE可以帮助你更轻松地阅读和理解源码&#xff0c;并提供跳转到源码定义处的功能。此外&#xff0c;你还需…

Oracle regexp_substr

select regexp_substr(123|456|789, [^|], 1, 2) from dual;

软件测试|快速、可靠的JavaScript依赖管理工具——yarn

简介 Yarn是一个由Facebook于2016年推出的JavaScript软件包管理器。它的目标是解决npm&#xff08;Node.js的默认软件包管理器&#xff09;在性能和可靠性方面的一些问题。Yarn旨在提供更快、更安全、更稳定的依赖项安装过程&#xff0c;使JavaScript开发人员能够更轻松地管理…

TortoiseSVN·文件锁定与清理

安装 TortoiseSVN 的时候&#xff0c;选择 svn 命令可用, 选择 will be intalled on local hard drive 。 在锁定的文件夹内 cmd 进入终端&#xff0c;输入 find . -type f -name ".svn/lock" -exec rm -f {} \; 删除所有锁定文件。进行清理操作&#xff1a;svn clea…

RHCE9学习指南 第18章 日志

日志中记录了各种各样的问题&#xff0c;所以读取日志是检测并排除故障的一个重要方式&#xff0c;日志文件默认放在/var/log/目录下。不同的问题要读取不同的日志&#xff0c;例如&#xff0c;邮件发不出去&#xff0c;可以读取日志文件件/var/log/maillog&#xff1b;要查看哪…

【38 Pandas+Pyecharts | 奥迪汽车销量数据分析可视化】

文章目录 &#x1f3f3;️‍&#x1f308; 1. 导入模块&#x1f3f3;️‍&#x1f308; 2. Pandas数据处理2.1 读取数据2.2 查看数据信息2.3 数据处理 &#x1f3f3;️‍&#x1f308; 3. Pyecharts数据可视化3.1 奥迪用户购车时间分布3.2 奥迪各系销量占比饼图3.3 奥迪各系销量…

外汇天眼:CQG 与 TradeStation Securities 的经纪服务集成

TradeStation Securities, Inc.&#xff0c;一家自营的在线股票、ETF、期权和期货交易经纪公司&#xff0c;宣布与CQG合作&#xff0c;CQG是一家为交易员、经纪商、商业套保者和交易所提供高性能技术解决方案的全球供应商&#xff0c;已与TradeStation Securities的经纪服务集成…

Zustand 状态管理

Zustand 状态管理 安装创建 Store给 Store 添加TS类型约束在页面使用 Store返回 Store 中所有状态在 Store 中使用 async 异步方法使用 Immer Middleware (中间件) 更新深层嵌套的 State使用 get 方法&#xff0c;在 set 方法外访问 State 中的数据使用 selector什么是 selecto…

GNN如何处理表格?

链接: https://ieeexplore.ieee.org/document/10184514 在这篇综述中&#xff0c;我们深入探讨了使用图神经网络&#xff08;GNNs&#xff09;进行表格数据学习&#xff08;TDL&#xff09;的领域&#xff0c;这是一个深度学习方法在分类和回归任务中相比传统方法表现出越来越…

Unity中BRP下的深度图

文章目录 前言一、在Shader中使用1、在使用深度图前申明2、在片元着色器中 二、在C#脚本中开启摄像机深度图三、最终效果 前言 在之前的文章中&#xff0c;我们实现了URP下的深度图使用。 Unity中URP下使用屏幕坐标采样深度图 在这篇文章中&#xff0c;我们来看一下BRP下深度…

2024-01-03 无重叠区间

435. 无重叠区间 思路&#xff1a;和最少数量引爆气球的箭的思路基本都是一致了&#xff01;贪心就是比较左边的值是否大于下一个右边的值 class Solution:def eraseOverlapIntervals(self, points: List[List[int]]) -> int:points.sort(keylambda x: (x[0], x[1]))# 比较…

2023-12-30 买卖股票的最佳时机 II和跳跃游戏以及跳跃游戏 II

122. 买卖股票的最佳时机 II 思路&#xff1a;关键点是每一次利用峰值来计算【画图好理解一点&#xff0c;就是计算陡坡的值】&#xff01;每一次累加和的最大! 或者可以这样理解&#xff0c;把利润划分为每天的&#xff0c;如假如第 0 天买入&#xff0c;第 3 天卖出&#xf…

Spring MVC 的controller方法返回值

controller方法返回值 返回ModelAndView 说明&#xff1a;controller方法中定义ModelAndView对象并返回&#xff0c;对象中可添加model数据、指定view 返回字符串 逻辑视图名 说明&#xff1a;controller方法返回字符串可以指定逻辑视图名&#xff0c;通过视图解析器解析为…

2024年最牛家用NAS+虚拟化方案,极低功耗

谈谈个人搭建NAS服务器有哪些需求 我有5台华为服务器、群晖、DX4600、路由器、交换机、小主机等&#xff0c;为了尝试出最牛NAS方案&#xff0c;前后投入了几万元&#xff0c;可谓是发烧友。 听我劝&#xff0c;照着这个方案执行&#xff0c;爽死你。 低功耗。NAS是长期运行的…

golang实现skiplist 跳表

跳表 package mainimport ("errors""math""math/rand" )func main() {// 双向链表///**先理解查找过程Level 3: 1 6Level 2: 1 3 6Level 1: 1 2 3 4 6比如 查找2 ; 从高层往下找;如果查找的值比当前值小 说明没有可查找的值2比1大 往当前…

window的两种监听方式区别

一、window.onmessage window.onmessage 是一个属性&#xff0c;直接赋值为事件处理函数。当使用这个方式时&#xff0c;只能绑定一个事件处理函数&#xff0c;如果后续再次赋值&#xff0c;会覆盖之前的处理函数。如果在同一个页面中只有一个地方需要处理消息&#xff0c;这种…

ELF文件格式解析二

使用objdump命令查看elf文件 objdump -x 查看elf文件所有头部的信息 所有的elf文件。 程序头部&#xff08;Program Header&#xff09;中&#xff0c;都以 PT_PHDR和PT_INTERP先开始。这两个段必须在所有可加载段项目的前面。 从上图中的INTERP段中&#xff0c;可以看到改段…