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)在派工自动化中发挥着重要作用,通过实时数据采集和智能调度,优化生…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.
ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #:…...
LangFlow技术架构分析
🔧 LangFlow 的可视化技术栈 前端节点编辑器 底层框架:基于 (一个现代化的 React 节点绘图库) 功能: 拖拽式构建 LangGraph 状态机 实时连线定义节点依赖关系 可视化调试循环和分支逻辑 与 LangGraph 的深…...

使用SSE解决获取状态不一致问题
使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件,这个上传文件是整体功能的一部分,文件在上传的过程中…...

篇章二 论坛系统——系统设计
目录 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 1. 数据库设计 1.1 数据库名: forum db 1.2 表的设计 1.3 编写SQL 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 通过需求分析获得概念类并结合业务实现过程中的技术需要&#x…...

【免费数据】2005-2019年我国272个地级市的旅游竞争力多指标数据(33个指标)
旅游业是一个城市的重要产业构成。旅游竞争力是一个城市竞争力的重要构成部分。一个城市的旅游竞争力反映了其在旅游市场竞争中的比较优势。 今日我们分享的是2005-2019年我国272个地级市的旅游竞争力多指标数据!该数据集源自2025年4月发表于《地理学报》的论文成果…...

GraphRAG优化新思路-开源的ROGRAG框架
目前的如微软开源的GraphRAG的工作流程都较为复杂,难以孤立地评估各个组件的贡献,传统的检索方法在处理复杂推理任务时可能不够有效,特别是在需要理解实体间关系或多跳知识的情况下。先说结论,看完后感觉这个框架性能上不会比Grap…...

CTF show 数学不及格
拿到题目先查一下壳,看一下信息 发现是一个ELF文件,64位的 用IDA Pro 64 打开这个文件 然后点击F5进行伪代码转换 可以看到有五个if判断,第一个argc ! 5这个判断并没有起太大作用,主要是下面四个if判断 根据题目…...