Qt中槽函数在那个线程执行的探索和思考
信号和槽是Qt的核心机制之一,通过该机制大大简化了开发者的开发难度。信号和槽属于观察者模式(本质上是回调函数的应用)。是函数就需要考虑其是在那个线程中执行,本文讨论的就是槽函数在那个线程中执行的问题。
目录
1. connect函数的第五个参数说明
2. 发送者和接收者在同一个线程创建
2.1 槽函数在发送者所在线程执行
2.3 槽函数不在发送者所在线程执行
3. 发送者和接收者在不同线程创建
3.1 槽函数在接收者所在线程执行
3.2 槽函数在发送者所在线程执行
4.发送者所在线程和发送信号线程的区别
1. connect函数的第五个参数说明
connect函数用来连接信号和槽,类似于提前注册。其第五个参数默认是Qt::AutoConnection,所以开发者很多时候可以忽略此参数。但是在牵扯到复杂业务开发,尤其是多线程并发开发时往往需要关注第五个参数,第五个参数取值和含义如下:
- Qt::AutoConnection
自动连接,发送者和接受者在同一个线程时等同于Qt::DirectConnection,不同线程等同于Qt::QueuedConnection
- Qt::DirectConnection
直接(同步)连接,槽函数在接受者所依附线程执行。
- Qt::QueuedConnection
队列(异步)连接,槽函数在发送信号的线程执行。异步连接的时候,信号是由接收者所在的线程的事件处理机制来处理的,如果接收者所在的线程没有事件处理的话,这个信号就不会被处理
- Qt::BlockingQueuedConnection
阻塞连接,发送者和接收者在同一线程时会死锁
- Qt::UniqueConnection
唯一连接,防止信号和槽重复连接
- Qt::SingleShotConnection
单次连接,信号和槽函数只连接一次,槽函数执行后连接会自动断开
2. 发送者和接收者在同一个线程创建
2.1 槽函数在发送者所在线程执行
发送者
sender.h
#ifndef SENDER_H
#define SENDER_H#include <QObject>
#include <QString>class Sender :public QObject {Q_OBJECTpublic:Sender(QObject* parent = 0);public slots :void emitsig(const QString& str, const int& ci);signals:void sig(const QString& str, const int& ci);
};#endif // SENDER_H
sender.cpp
#include "sender.h"
#include <QDebug>
#include <QThread>
#include <iostream>
#include <thread>
#include <QObject>Sender::Sender(QObject* parent) : QObject(parent) {}void Sender::emitsig(const QString& str, const int& ci)
{qDebug() << "sender signal thread id is: " << QThread::currentThreadId()<< str << ci;emit sig(str, ci);
}
接收者
recver.h
#ifndef RECVER_H
#define RECVER_H#include <QObject>
#include <QDebug>
#include <QThread>
#include <iostream>
#include <thread>
#include <QObject>class Recver : public QObject
{Q_OBJECT
public:explicit Recver(QObject *parent = nullptr);public slots :void slot(const QString& str, const int& ci);
};#endif // RECVER_H
recver.cpp
#include "recver.h"
#include <QDebug>
#include <QThread>
#include <iostream>
#include <thread>
#include <QObject>Recver::Recver(QObject *parent): QObject{parent}
{}void Recver::slot(const QString& str, const int& ci) {qDebug() << "recver slot thread id is: " << QThread::currentThreadId()<< str << ci;
}
main函数
main.cpp
#include <QCoreApplication>
#include <QDebug>
#include <QThread>
#include <iostream>
#include <thread>
#include <QObject>#include "sender.h"
#include "recver.h"
#include "mythread.h"/*
// QObject::connect函数的第五个参数:Qt::AutoConnection:自动连接,发送者和接受者在同一个线程时等同于Qt::DirectConnection,不同线程等同于Qt::QueuedConnectionQt::DirectConnection:直接调用连接,槽函数在接受者所依附线程执行。Qt::QueuedConnection:异步调用连接,槽函数在发送信号的线程执行。异步连接的时候,信号是由接收者所在的线程的事件处理机制来处理的,如果接收者所在的线程没有事件处理的话,这个信号就不会被处理Qt::BlockingQueuedConnection:阻塞连接调用,发送者和接收者在同一线程时会死锁Qt::UniqueConnection:防止信号和槽重复连接Qt::SingleShotConnection:信号和槽函数仅只需单次连接,槽函数执行后连接会自动断开
*//*
// Qt4和Qt5都适用的连接方式,注意:此方式是在Q5上可能会导致槽函数不被调用,此时可尝试使用Qt5新的连接方式
QObject::connect(&sender, SIGNAL(sig()), &recver, SLOT(slot()), Qt::DirectConnection);
QObject::connect(&sender, SIGNAL(sig()), &recver, SLOT(slot()), Qt::QueuedConnection);
//QObject::connect(&sender, SIGNAL(sig()), &recver, SLOT(slot()), Qt::BlockingQueuedConnection);
QObject::connect(&sender, SIGNAL(sig()), &recver, SLOT(slot()), Qt::UniqueConnection);
QObject::connect(&sender, SIGNAL(sig()), &recver, SLOT(slot()), Qt::SingleShotConnection);
*//*
// Qt5最新的连接方式
QObject::connect(&sender, &Sender::sig, &recver, &Recver::slot, Qt::DirectConnection);
QObject::connect(&sender, &Sender::sig, &recver, &Recver::slot, Qt::QueuedConnection);
// 发送者和接收者在同一个线程,将会死锁
//QObject::connect(&sender, &Sender::sig, &recver, &Recver::slot, Qt::BlockingQueuedConnection);
//QObject::connect(&sender, &Sender::sig, &recver, &Recver::slot, Qt::UniqueConnection);
QObject::connect(&sender, &Sender::sig, &recver, &Recver::slot, Qt::SingleShotConnection);
*/// 主线程获取
/*
QCoreApplication::instance()->thread();
*//*
// 关于Qt线程的使用说明
在Qt4.3(包括)之前,run 是虚函数,必须子类化QThread来实现run函数。
而从Qt4.4开始,qthreads-no-longer-abstract,run 默认调用 QThread::exec() 。这样一来不需要子类化 QThread 了,只需要子类化一个 QObject就够了
QThread中run对于线程的作用相当于main函数对于应用程序。它是线程的入口,run的开始和结束意味着线程的开始和结束。
QThread所依附的线程,就是创建线程的线程。
QThread管理的线程,就是run中创建的线程。
*/// 连接多次,发送信号槽函数也会多次触发
void test1() {Sender sender;Recver recver;QObject::connect(&sender, &Sender::sig, &recver, &Recver::slot);QObject::connect(&sender, &Sender::sig, &recver, &Recver::slot);qDebug() << "call first";sender.emitsig("fist", 110);qDebug() << "call second";sender.emitsig("second", 220);qDebug() << "call third";sender.emitsig("third", 330);
}// 接受对象虽然是线程对象,但是槽函数却在发送对象所在线程对象执行,因为接收者依附于发送对象所在线程
/*
QThread中slot和run函数共同操作的对象,都会用QMutex锁住是因为slot和run处于不同线程,需要线程间的同步。
*/
void test2() {Sender sender;Mythread mythread;// Mythread构造函数中去掉moveToThread(this);,槽函数将在主线程执行,加上moveToThread(this)槽函数将在子线程执行// 加上moveToThread(this)后连接方式修改为Qt::DirectConnection,槽函数在主线程中执行QObject::connect(&sender, SIGNAL(sig(const QString&, const int&)), &mythread, SLOT(slot_main(const QString&, const int&)));mythread.start();sender.emitsig("mythread", 440);
}int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);qDebug() << "main thead id is : " << QThread::currentThreadId();//std::cout << std::this_thread::get_id() << std::endl;Sender sender;Recver recver;QObject::connect(&sender, &Sender::sig, &recver, &Recver::slot);sender.emitsig("fist", 110);return a.exec();
}
运行效果:
可以看到槽函数在信号发送者所在线程(主线程)中执行。
2.3 槽函数不在发送者所在线程执行
main.cpp
#include <QCoreApplication>
#include <QDebug>
#include <QThread>
#include <iostream>
#include <thread>
#include <QObject>#include "sender.h"
#include "recver.h"int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);qDebug() << "main thead id is : " << QThread::currentThreadId();//std::cout << std::this_thread::get_id() << std::endl;Sender sender;Recver recver;QObject::connect(&sender, &Sender::sig, &recver, &Recver::slot);QThread thread;recver.moveToThread(&thread);thread.start();sender.emitsig("fist", 110);return a.exec();
}
运行效果:
代码中创建一个子线程对象,并将接收者移动到子线程,槽函数在子线程中执行。
3. 发送者和接收者在不同线程创建
3.1 槽函数在接收者所在线程执行
mythread.h
#ifndef MYTHRED_H
#define MYTHRED_H#include <QThread >class Mythread : public QThread
{
Q_OBJECTpublic:Mythread(QObject* parent = 0);public slots:void slot_main(const QString& str, const int& ci);protected:void run();
};#endif // MYTHRED_H
mythread.cpp
#include "mythread.h"
#include <QDebug>
#include "sender.h"Mythread::Mythread(QObject* parent) : QThread(parent)
{//moveToThread(this);
}void Mythread::slot_main(const QString& str, const int& ci) {qDebug() << "mythread slot_main: " << currentThreadId() << str << ci;
}void Mythread::run() {qDebug() << "mythread thread: " << currentThreadId();Sender sender;connect(&sender, SIGNAL(sig(const QString&, const int&)), this, SLOT(slot_main(const QString&, const int&)));sender.emitsig("thread", 220);exec();
}
main.cpp
#include <QCoreApplication>
#include <QDebug>
#include <QThread>
#include <iostream>
#include <thread>
#include <QObject>#include "sender.h"
#include "recver.h"
#include "mythread.h"int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);qDebug() << "main thead id is : " << QThread::currentThreadId();//std::cout << std::this_thread::get_id() << std::endl;Mythread mythread;mythread.start();return a.exec();
}
运行效果如下:
如上显示在子线程发送信号,槽函数却在主线程中执行,因为接收者mythread是在主线程中创建。
3.2 槽函数在发送者所在线程执行
mythread.cpp
#include "mythread.h"
#include <QDebug>
#include "sender.h"Mythread::Mythread(QObject* parent) : QThread(parent)
{moveToThread(this);
}void Mythread::slot_main(const QString& str, const int& ci) {qDebug() << "mythread slot_main: " << currentThreadId() << str << ci;
}void Mythread::run() {qDebug() << "mythread thread: " << currentThreadId();Sender sender;connect(&sender, SIGNAL(sig(const QString&, const int&)), this, SLOT(slot_main(const QString&, const int&)));sender.emitsig("thread", 220);exec();
}
main.cpp
#include <QCoreApplication>
#include <QDebug>
#include <QThread>
#include <iostream>
#include <thread>
#include <QObject>#include "sender.h"
#include "recver.h"
#include "mythread.h"int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);qDebug() << "main thead id is : " << QThread::currentThreadId();//std::cout << std::this_thread::get_id() << std::endl;Mythread mythread;mythread.start();return a.exec();
}
运行效果:
将信号接收者构造函数//moveToThread(this);的注释放开,槽函数在子线程中执行。尽管接收者在主线程中被创建。
如果构造函数注释moveToThread(this);,connect第五个参数修改为Qt::DirectConnection,槽函数也可以在子线程中执行,如下:
connect(&sender, SIGNAL(sig(const QString&, const int&)), this, SLOT(slot_main(const QString&, const int&)), Qt::DirectConnection);
mythread.cpp
#include "mythread.h"
#include <QDebug>
#include "sender.h"Mythread::Mythread(QObject* parent) : QThread(parent)
{//moveToThread(this);
}void Mythread::slot_main(const QString& str, const int& ci) {qDebug() << "mythread slot_main: " << currentThreadId() << str << ci;
}void Mythread::run() {qDebug() << "mythread thread: " << currentThreadId();Sender sender;connect(&sender, SIGNAL(sig(const QString&, const int&)), this, SLOT(slot_main(const QString&, const int&)), Qt::DirectConnection);sender.emitsig("thread", 220);exec();
}
信号发送者在子线程,槽函数也在子线程中执行。
4.发送者所在线程和发送信号线程的区别
有很多人分不清发送者所在线程和发送信号的线程,在此做一下区分说明。发送者所在线程指的是构造发送者对象所在的线程,发送信号线程指的是发送信号所在的线程,即调用emit所在的线程,有些时候这两个线程并不是一个线程。前面说的槽函数在发送者所在线程执行指的是在发送者所在线程,而不是发送信号的线程。如下代码:
mythread.h
#ifndef MYTHRED_H
#define MYTHRED_H#include <QThread>
#include "sender.h"class Mythread : public QThread
{
Q_OBJECTpublic:Mythread(QObject* parent = 0);Mythread(Sender* sender);public slots:void slot_main(const QString& str, const int& ci);protected:void run();private:Sender* m_sender;
};#endif // MYTHRED_H
mythread.cpp
#include "mythread.h"
#include <QDebug>Mythread::Mythread(QObject* parent) : QThread(parent)
{//moveToThread(this);m_sender = nullptr;
}Mythread::Mythread(Sender* sender) {//moveToThread(this);m_sender = sender;
}void Mythread::slot_main(const QString& str, const int& ci) {qDebug() << "mythread slot_main: " << currentThreadId() << str << ci;
}void Mythread::run() {qDebug() << "mythread thread: " << currentThreadId();if (m_sender) {connect(m_sender, SIGNAL(sig(const QString&, const int&)), this, SLOT(slot_main(const QString&, const int&)));m_sender->emitsig("thread", 220);}exec();
}
main.cpp
#include <QCoreApplication>
#include <QDebug>
#include <QThread>
#include <iostream>
#include <thread>
#include <QObject>#include "sender.h"
#include "recver.h"
#include "mythread.h"int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);qDebug() << "main thead id is : " << QThread::currentThreadId();//std::cout << std::this_thread::get_id() << std::endl;Sender sender;Mythread mythread(&sender);mythread.start();return a.exec();
}
运行效果如下:
如上,发送者对象在主线程创建,在子线程中发送信号,槽函数是在主线程中执行,而并非在子线程中执行。
相关文章:

Qt中槽函数在那个线程执行的探索和思考
信号和槽是Qt的核心机制之一,通过该机制大大简化了开发者的开发难度。信号和槽属于观察者模式(本质上是回调函数的应用)。是函数就需要考虑其是在那个线程中执行,本文讨论的就是槽函数在那个线程中执行的问题。 目录 1. connect…...

C++ 类模板
目录 前言 类模板语法 类模板和函数模板的区别 类模板没有自动类型推导的使用方式 类模板在模板参数列表中可以有默认参数 类模板中成员函数创建时机 类模板对象做函数参数 指定传入的类型 参数模板化 整个类模板化 类模板与继承 类模板成员函数类外实现 类模板分…...

边缘计算系统设计与实践
随着科技的飞速发展,物联网和人工智能两大领域的不断突破,我们看到了一种新型的计算模型——边缘计算的崛起。这种计算模型在处理大规模数据、实现实时响应和降低延迟需求方面,展现出了巨大的潜力。本文将深入探讨边缘计算系统的设计原理和实…...

【Spark精讲】Spark存储原理
目录 类比HDFS的存储架构 Spark的存储架构 存储级别 RDD的持久化机制 RDD缓存的过程 Block淘汰和落盘 类比HDFS的存储架构 HDFS集群有两类节点以管理节点-工作节点模式运行,即一个NameNode(管理节点)和多个DataNode(工作节点)。 Namenode管理文件系统的命名空…...

贪心算法:买卖股票的最佳时机II 跳跃游戏 跳跃游戏II
122.买卖股票的最佳时机II 思路: 想要获得利润,至少要以两天为一个交易单元,因为两天才会有股价差。因此可以将最终利润进行分解,如prices[3] - prices[0] (prices[3] - prices[2]) (prices[2] - prices[1]) (prices[1] - pr…...

音频DAC,ADC,CODEC的选型分析,高性能立体声
想要让模拟信号和数字信号顺利“交往”,就需要一座像“鹊桥”一样的中介,将两种不同的语言转变成统一的语言,消除无语言障碍。这座鹊桥就是转换器芯片,也就是ADC芯片。ADC芯片的全称是Analog-to-Digital Converter, 即模拟数字转换…...

python 连接SQL server 请用pymssql连接,千万别用pyodbc
pymssql官方介绍文档 python 使用 pymssql连接 SQL server 代码示例: 安装pymssql包: pip install pymssql代码: import pymssqldef conn_sqlserver_demo():# 连接字符串示例(根据您的配置进行修改)conn Nonetry:co…...

IntelliJ IDEA 自带HTTP Client接口插件上传文件示例
如何使用IntelliJ IDEA自带的HTTP Client接口插件进行文件上传的示例。在这个示例中,我们将关注Controller代码、HTTP请求文件(xxx.http),以及文件的上传和处理。 Controller代码 首先,让我们看一下处理文件上传的Co…...
C++中的接口有什么用
2023年12月13日,周三上午 今天上午在适配器模式,我发现如果想真正理解适配器模式,就必须学会使用C中的接口,就必须明白为什么要在C中使用接口,所以重新学习了一下C中的接口 目录 C中的接口有什么用用代码说明“实现多…...

el-table合并相同数据的单元格
相同的数据合并单元格 <el-table :data"userList" :span-method"objectSpanMethod" border><el-table-column type"selection" width"50" align"center" /><el-table-column label"用户名称" a…...

Verilog Systemverilog define宏定义
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 文章前情预告一、define是个啥?二、为什么要使用define三、怎么使用define四、define的横向拓展五、define思想在生活中的体现!六、结论七、参考资料八、…...

51单片机应用从零开始(十一)·数组函数、指针函数
51单片机应用从零开始(九)数组-CSDN博客 51单片机应用从零开始(十)指针-CSDN博客 目录 1. 用数组作函数参数控制流水花样 2. 用指针作函数参数控制 P0 口 8 位 LED 流水点亮 1. 用数组作函数参数控制流水花样 要在51单片机中…...
【PostgreSQL】从零开始:(八)PostgreSQL-数据库PSQL元命令
元命令 postgres# \? General\bind [PARAM]... set query parameters\copyright show PostgreSQL usage and distribution terms\crosstabview [COLUMNS] execute query and display result in crosstab\errverbose show most recent error…...
02 使用Vite创建Vue3项目
概述 A Vue project is structured similarly to a lot of modern node-based apps and contains the following: A package.json fileA node_modules folder in the root of your projectVarious other configuration files are usually contained at the root level, such …...
Shell三剑客:sed(简介)
一、前言 Stream EDitor:流编辑 sed 是一种在线的、非交互式的编辑器,它一次处理一行内容。处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后&…...
tp连接数据库
ThinkPHP内置了抽象数据库访问层,把不同的数据库操作封装起来,我们只需要使用公共的Db类进行操作,而无需针对不同的数据库写不同的代码和底层实现,Db类会自动调用相应的数据库驱动来处理。采用PDO方式,目前包含了Mysql…...

jmeter,断言:响应断言、Json断言
一、响应断言 接口A请求正常返回值如下: {"status": 10013, "message": "user sign timeout"} 在该接口下创建【响应断言】元件,配置如下: 若断言成功,则查看结果树的接口显示绿色,若…...

dockerfite创建镜像---INMP+wordpress
搭建dockerfile---lnmp 在192.168.10.201 使用 Docker 构建 LNMP 环境并运行 Wordpress 网站平台 [rootdocker1 opt]# mkdir nginx mysql php [rootdocker1 opt]# ls #分别拖入四个包: nginx-1.22.0.tar.gz mysql-boost-5.7.20.tar.gz php-7.1.10.tar.bz2 wor…...

服务器数据恢复—raid5热备盘未激活崩溃导致上层oracle数据丢失的数据恢复案例
服务器数据恢复环境: 某品牌X系列服务器,4块SAS硬盘组建了一组RAID5阵列,还有1块磁盘作为热备盘使用。服务器上层安装的linux操作系统,操作系统上部署了一个基于oracle数据库的OA(oracle已经不再为该OA系统提供后续服务…...

生产派工自动化:MES系统的关键作用
随着制造业的数字化转型和智能化发展,生产派工自动化成为了提高生产效率、降低成本,并实现优质产品生产的关键要素之一。制造执行系统(MES)在派工自动化中发挥着重要作用,通过实时数据采集和智能调度,优化生…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...

定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...

Golang——7、包与接口详解
包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...
日常一水C
多态 言简意赅:就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过,当子类和父类的函数名相同时,会隐藏父类的同名函数转而调用子类的同名函数,如果要调用父类的同名函数,那么就需要对父类进行引用&#…...