Qt多线程中的信号与槽

2023-05-16

1. Qt对象的依附性和事务循环

  QThread继承自QObject,自然拥有发射信号/定义槽函数的能力。QThread默认声明了以下几个关键信号(信号只能声明不能定义):
  (1) 线程开始运行时发射的信号

void started()

  (2) 线程完成运行时发射的信号

void finished()

  (3) 线程被异常终止时发射的信号

void terminated()

  多线程中的信号与槽之间的关系,是Qt编程中一个相对复杂的内容。先看下面例子:

//TestThread.h声明了线程类
#ifndef TESTTHREAD_H
#define TESTTHREAD_H

#include <QThread>
#include <QObject>
#include <QDebug>

//在线程类TestThread中自定义信号和槽函数
class TestThread : public QThread
{
    Q_OBJECT

protected:
    void run(); //线程执行函数

public:
    explicit TestThread(QObject *parent = 0);

signals:
    void TestThread_Signal();   //自定义的信号

public slots:
    void TestThread_Slot();     //对应TestThread_Signal()信号的槽函数
};

#endif // TESTTHREAD_H

//TestThread.cpp
#include "TestThread.h"
#include <QDebug>

TestThread::TestThread(QObject *parent) :
    QThread(parent)
{
    //连接信号与槽
    connect(this, SIGNAL(TestThread_Signal()), this, SLOT(TestThread_Slot()));
}

//子线程执行函数
void TestThread::run()
{
    //打印新线程的ID值
    qDebug() << "void TestThread::run(): tid = " << currentThreadId();

    //发射自定义信号
    emit TestThread_Signal();
}

void TestThread::TestThread_Slot()
{
    //打印此函数运行时的所在线程的ID值
    qDebug() << "TestThread::TestThread_Slot(): tid = " << currentThreadId();
}

//main.c
#include <QtCore/QCoreApplication>
#include "TestThread.h"
#include "MyObject.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main: tid = " << QThread::currentThreadId();

    TestThread t;

    //开启线程执行函数,run()得到执行
    t.start();

    return a.exec();
}

  运行:
这里写图片描述

  上面代码的运行结果:
  槽函数执行时的所在线程和信号发送操作的所在线程并不是同一个,前者位于main线程中,后者位于子线程中。

  由此可以引申两个问题:
  (1) 二者同属于子线程类,程序运行时发送信号操作在子线程完成,对应的槽函数却是在main线程执行,究其原因,得从Qt对象的依附性说起。
  在Qt编程中,默认情况下,对象依附于创建自身的线程,例如上面代码中TestThread对象t它是在main()函数中创建的,那么t依附于主线程,而槽函数在其所依附的线程中被调用执行,因此,槽函数TestThread_Slot()是在main线程中执行。

  要想让TestThread_Slot()函数运行在main()创建的子线程中,可以使用moveToThread()函数更改TestThread对象所依附的线程:

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main: tid = " << QThread::currentThreadId();

    TestThread t;

    t.moveToThread(&t);     //更改TestThread对象的依附性到t线程

    t.start();

    return a.exec();
}

  运行:
这里写图片描述

  需要强调,这样的做法在实际工程中并不采取。因为QThread应该被看做是操作系统线程的接口或控制点,而不应该包含需要在新线程中运行的业务逻辑代码。显然,修改TestThread对象所依附的线程恰恰是打算在线程类对象中实现业务逻辑代码。

  (2) 若要对上面的代码的运行结果做个转换:槽函数运行时所依赖的是子线程,信号发送操作是在main()线程中完成,何以实现?

//定义一个存放槽函数的类MyObject,它继承自QObject
//MyObject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <QObject>
#include <QThread>
#include <QDebug>

class MyObject : public QObject
{
    Q_OBJECT
public:
    explicit MyObject(QObject *parent = 0);

public slots:           //定义两个槽函数
    void getStarted();
    void MyObjectSlot();
};

#endif // MYOBJECT_H

//MyObject.cpp
#include "MyObject.h"

MyObject::MyObject(QObject *parent) :
    QObject(parent)
{
}

void MyObject::getStarted()
{
    qDebug() << "MyObject::getStarted(): tid = " << QThread::currentThreadId();
}

void MyObject::MyObjectSlot()
{
    qDebug() << "MyObject::MyObjectSlot(): tid = " << QThread::currentThreadId();
}

在main函数中,定义线程对象和MyObject对象,并将MyObject的依附性改为子线程
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main: tid = " << QThread::currentThreadId();

    TestThread t;
    MyObject m;

    //m.moveToThread(&t);
    QObject::connect(&t, SIGNAL(TestThread_Signal()), &m, SLOT(MyObjectSlot()));
    t.start();      //run()函数的实现体和上面的一样,会发射TestThread_Signal()信号。该信号在这里还增加连接了MyObjectSlot的槽,所以除了会触发
                    //TestThread_Slot()函数外还会触发MyObjectSlot()

    return a.exec();
}

  运行:
这里写图片描述

  TestThread::TestThread_Slot()得到调用,但是MyObject::MyObjectSlot()却没有,这就要引入事件循环的概念。当发送信号操作所在的线程和槽函数执行时所在的线程是不同线程时,就需要事件循环的支持。如main函数中的”a.exec()”函数的调用也是开启事件循环,QThread线程类同样提供了exec()函数用于开启线程的事件循环,发送信号和槽函数位于不同的线程时,只有槽函数所在线程开启了事件循环,它才能在对应信号发射后被调用。无论事件循环是否开启,信号发送后会直接进入槽函数所依附的线程的事件队列,然而,只有开启了事件循环,对应的从函数才会在线程中得到调用。

这里写图片描述

  从上图可知,事件循环是一个无止尽循环,事件循环结束之前,exec()函数后的语句无法得到执行。问线程如何正常结束?这就需要quit()或者载exit()函数用于结束事件循环。

  在TestThread::run()函数中增加exec()函数的调用:

void TestThread::run()
{
    qDebug() << "void TestThread::run(): tid = " << currentThreadId();

    emit TestThread_Signal();

    exec(); //开启子线程的事件循环
    qDebug() << "hello";
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main: tid = " << QThread::currentThreadId();

    TestThread t;
    MyObject m;


    m.moveToThread(&t);

    QObject::connect(&t, SIGNAL(TestThread_Signal()), &m, SLOT(MyObjectSlot()));

    t.start();          //开启线程执行函数
    t.wait(8 * 1000);   //休眠等待8s,确保事件队列中的槽函数得到执行后,再让子线程结束事件循环
    t.quit();

    return a.exec();
}

  运行:
这里写图片描述

  需要注意的是,当信号的发送与对象的槽函数的执行在不同线程中时,可能会产生临界资源的竞争问题。

2. 信号与槽的连接方式

  信号与槽的连接函数的原型为:

bool QObject::connect (const QObject * sender, 
                        const char * signal, 
                        const QObject * receiver,
                        const char * method,
                        Qt::ConnectionType type = Qt::AutoConnection)

  其中第5个参数决定信号与槽的连接方式,用于决定槽函数被调用时的相关行为。

Qt::AutoConnection              默认连接
Qt::DirectConnection            槽函数立即调用
Qt::BlockingQueuedConnection    同步调用
Qt::QueuedConnection            异步调用
Qt::UniqueConnection            单一连接

  (1) Qt::DirectConnection(立即调用)
  直接在发送信号的线程中调用槽函数(发送信号和槽函数位于同一线程),等价于槽函数的实时调用。

  (2) Qt::QueuedConnection(异步调用)
  信号发送至目标线程的事件队列(发送信号和槽函数位于不同线程),交由目标线程处理,当前线程继续向下执行。

  (3) Qt::BlockingQueuedConnection(同步调用)
  信号发送至目标线程的事件队列,由牧宝想线程处理。当前线程阻塞等待槽函数的返回,之后向下执行。

  (4) Qt::AutoConnection(默认连接)
  当发送信号线程=槽函数线程时,效果等价于Qt::DirectConnection;
  当发送信号线程!=槽函数线程时,效果等价于Qt::QueuedConnection。
  Qt::AutoConnection是connect()函数第5个参数的默认值,也是实际开发中字常用的连接方式。

  (5) Qt::UniqueConnection(单一连接)
  功能和AutoConnection相同,同样能自动确定连接类型,但是加了限制:同一个信号和同一个槽函数之间只能有一个连接。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Qt多线程中的信号与槽 的相关文章

  • 如何将顶点数据保存为STL文件?

    stl 文件是在计算机图形应用系统中 xff0c 用于表示三角形网格的一种文件格式 它的文件格式非常简单 xff0c 应用很广泛 STL是最多快速模型系统所应用的标准文件类型 STL是用三角网格来表现3D CAD模型 STL只能用来表示封闭
  • 常见图像噪声及产生原因(高斯、泊松和椒盐噪声)

    目录 什么是图像噪声噪声来源常见噪声高斯噪声泊松噪声乘性噪声椒盐噪声 信噪比 什么是图像噪声 噪声在图像上常表现为一引起较强视觉效果的孤立像素点或像素块 一般 xff0c 噪声信号与要研究的对象不相关 xff0c 它以无用的信息形式出现 x
  • 音视频OSD——修改叠加信息的位置

    目录 分析 叠加代码分析 总结 同一行中叠加 代码 效果 不同行中叠加 现象 代码 效果 纵向保护 代码 效果 总结 代码 c文件 h文件 效果 分析 之前的OSD叠加的位置是默认的 在初始位置 若想改动OSD叠加的位置 在叠加时进行位置的
  • 音视频OSD——修改叠加在yuv420p图像上信息的颜色

    目录 预备知识 准备图片 分析 映射关系 代码 效果 代码优化 c h 效果 预备知识 字符编码 一些基本概念 字符编码 详解常用字符集 ASCII ISO8859 1 GB2312 GBK Unicode 和字符编码 UTF 8 UTF
  • H.264——使用H.264视频编解码器JM进行YUV图像序列的编解码

    目录 常见H 264视频编码器JM基础配置准备一个YUV视频 JM实现编码修改配置文件Encoder Control编译运行 JM实现解码如何判断编码解码是否正确 常见H 264视频编码器 X264 xff08 只有编码没有解码 xff09
  • 有意思的题:(++a * a++)、(b++ * ++b)、(c++ * c++)、(++d * ++d)

    include lt stdio h gt int main int a 61 3 b 61 3 c 61 3 d 61 3 int a1 b1 c1 d1 a1 61 43 43 a a 43 43 b1 61 b 43 43 43 43
  • 详解LVDS通信协议

    目录 LVDS概述LVDS接口电路的组成LVDS输出接口电路类型单路6位LVDS输出接口双路6位LVDS输出接口单路8位1TL输出接口双路8位1TL输出位接口 典型LVDS发送芯片介绍四通道LVDS发送芯片五通道LVDS发送芯片十通道LVD
  • 自动跟随机器人:一种简易的自动跟随方案,自动跟随小车、自动跟随平衡小车、STM32、基于超声波的自动跟随小车

    目的 xff1a 一种廉价的跟随方案 xff0c 让大家都能够参与进来 xff0c 技术难度不大 xff0c 一些人也能够DIY一些属于自己的 跟随 机器人 xff01 并不是要做工业应用什么的 只是做出来玩玩 1 介绍 先看视频 xff0
  • 详解MIPI协议

    目录 前言MIPI简介MIPI联盟的MIPI DSI规范MIPI名词解释MIPI DSI分层结构command和video模式 D PHYLane模组Lane 全局架构Lane电压和状态DATA LANE操作模式时钟LANE低功耗状态高速数
  • 音频处理——详解PCM数据格式

    目录 知识储备什么是PCM采样采样率重采样 量化编码PCM常用指标 PCM数据流 知识储备 音频处理 音频编码原理简介 音频处理 音频处理的基本概念 什么是PCM PCM全称Pulse Code Modulation xff0c 翻译一下是
  • 音频处理——常用音频编码格式简介(PCM、G726、ADPCM、LPCM、G711、AAC)

    目录 PCMG726ADPCMLPCMG711AAC格式对比音频帧长音频播放过程 PCM 音频处理 详解PCM数据格式 音频处理 解析PCM格式实例 xff08 音量调控 xff09 G726 G 726是ITU T定义的音频编码算法 19
  • 音频处理——G711标准详解

    目录 G711简介G711A算法原理压缩方法举例代码 G711U算法原理压缩方法举例代码 G711A与G711U对比 参考链接 G711简介 G711是国际电信联盟ITU T定制出来的一套语音压缩标准 xff0c 它代表了对数PCM xff
  • PS流详解(载荷H264)

    目录 PS简介标准结构标准H264流结构定长音频帧和其他流式私有数据的结构 PS流封装标准PSH结构PES包结构PSM包结构体 元素流 PS 封装规则H264元素流封装规则音频元素流封装规则私有信息封装规则 PS简介 PS 封装方式需要支持
  • Postman中的authorization

    1 概述 Authorization是验证是否拥有从服务器访问所需数据的权限 当发送请求时 xff0c 通常必须包含参数 xff0c 以确保请求具有访问和返回所需数据的权限 Postman提供了授权类型 xff0c 可以轻松地在Postma
  • 操作pdf,提示startxref not found

    startxref not found多半是文件被损坏了 xff0c 检查一下 xff0c 是不是之前自己写的代码把pdf文件跑崩了 可以尝试重新生成一遍该pdf文件 xff0c 然后再进行操作 或者尝试一下 xff1a https www
  • FTP 530未登录

    提供一种思路 xff1a 如果说FTP服务器已开 xff0c 服务器也能ping通 就得考虑是不是我们在FTP服务器上设置的默认路径有问题 xff08 不符合我们的需求 xff09 Windows10下 xff0c FTP设置默认位置 xf
  • 开源个小demo

    https github com UnderADome epms 内部项目管理
  • LDAP的基本知识

    https zhuanlan zhihu com p 147768058 https www cnblogs com gaoyanbing p 13967860 html
  • 「权威发布」2019年电赛最全各类题目细节问题解答汇总

    点击上方 大鱼机器人 xff0c 选择 置顶 星标公众号 福利干货 xff0c 第一时间送达 xff01 各位朋友大家上午好 xff0c 今天是比赛的第二天 xff0c 许多朋友都给我发消息 xff0c 我不是不回 xff0c 我实在是回不
  • Unable to find explicit activity class

    做项目从一个activity逐渐转向到使用多个activity xff0c 这个时候新手就容易出现一个问题 xff0c 忘了给activity在AndroidManifest xml中注册 打开日志 xff0c 在遇到这个报错信息的时候 x

随机推荐

  • Errors running builder 'Maven Project Builder'

    由于第一次玩maven的时候 xff0c 很多东西都还是懵懵懂懂 xff0c 不是很清楚 xff0c 不知道怎么把Myeclipse中的maven配置弄坏了 xff0c 从外部导入maven项目的时候 xff0c 总会报一些错误 xff1a
  • Type handler was null on parameter mapping for property '__frch_id_0'

    1 Type handler was null on parameter mapping for property frch id 0 2 Type handler was null on parameter mapping or prop
  • 如何解决error: failed to push some refs to 'xxx(远程库)'

    在使用git 对源代码进行push到gitHub时可能会出错 xff0c 信息如下 此时很多人会尝试下面的命令把当前分支代码上传到master分支上 git push u origin master 但依然没能解决问题 出现错误的主要原因是
  • expected an indented block

    Python中没有分号 xff0c 用严格的缩进来表示上下级从属关系 导致excepted an indented block这个错误的原因一般有两个 xff1a 1 冒号后面是要写上一定的内容的 xff08 新手容易遗忘这一点 xff09
  • C 实现TCP服务端(select、poll、epoll)

    使用C简单的实现一个tcp server xff0c 包括常规server 多线程实现server select实现server poll实现server epoll实现server IO模型原理可以看上一篇文章 常规模式 define M
  • UART串口通信

    目录 一 通信特点二 通信应用三 接线示意图三 UART通信协议四 STM32F4 串口使用1 资源分布2 特性3 UART框图4 使用方法5 相关库函数6 函数实例 五 实战 上位机控制开发板小灯 一 通信特点 异步 串行 全双工 一般描
  • 项目:文件搜索助手(FileSeeker)

    目录 1 项目简介 2 项目源代码 3 相关技术 4 实现原理 5 项目架构图 6 项目功能 7 测试报告 7 1 测试用例 7 2 测试环境 7 3 测试结论 7 3 1 功能测试 7 3 2 性能测试 7 3 3 兼容性 7 3 4 容
  • cocos2d实现2D地图A*广度路径算法

    h ifndef HELLOWORLD SCENE H define HELLOWORLD SCENE H include 34 cocos2d h 34 USING NS CC enum PatchFront Uper 61 1 Down
  • Keil 中,仿真调试查看局部变量值总是显示<not in scope>

    原因 xff1a 编译器把代码优化掉了 xff0c 直接导致在仿真中变量根本没有分配内存 xff0c 也就无法查看变量值 以后调试中遇到这种情况的解决办法 xff1a 核心思想是 xff1a 让变量值在代码中被读取其内存值 1 把变量定义为
  • 联合体在串口通讯中的妙用

    背景 本文主要涉及到的是一种串口通讯的数据处理方法 xff0c 主要是为了解决浮点数在串口通讯中的传输问题 xff1b 通常而言 xff0c 整形的数据类型 xff0c 只需进行移位运算按位取出每个字节即可 xff0c 那么遇到浮点型的数据
  • Linux 平均负载

    本文首发自公众号 LinuxOK xff0c ID 为 xff1a Linux ok 关注公众号第一时间获取更新 xff0c 分享不仅技术文章 xff0c 还有关于职场生活的碎碎念 在 Linux 系统中 xff0c 所谓平均负载 xff0
  • Linux 进程状态

    Linux 进程状态是平时排查问题 程序稳定性测试的基础知识 xff0c 查看进程状态的常用工具有 top 和 ps 以 top 的输出为例 xff1a S 列 xff08 Status xff09 表示进程的状态 xff0c 图中可见 D
  • Docker 是什么

    本文首发自公众号 LinuxOK xff0c ID为 xff1a Linux ok xff0c 关注公众号第一时间获取更新 xff0c 分享记录职场开发过程中所见所感 Docker 是一个用 GO 语言实现的开源项目 xff0c 它可以将应
  • 哈希表示例

    哈希表的意义在于高效查找 对于查找来说 xff0c 如果数据量特别大 xff0c 二分查找和哈希表算法十分有用了 二分查找前面已经讲过 xff0c 现来讲讲哈希表算法 就像输入数据数组下标返回数组元素一样 xff0c 这样的查找方式是最高效
  • RS-485通讯协议

    1 硬件层协议 通讯协议主要是实现两个设备之间的数据交换功能 xff0c 通讯协议分硬件层协议和软件层协议 硬件层协议决定数据如何传输问题 xff0c 比如要在设备1向设备2发送0x63 xff0c 0x63的二进制数为0110 0011
  • udp通讯中的connect()和bind()函数

    本文收录于微信公众号 LinuxOK xff0c ID为 xff1a Linux ok xff0c 关注公众号第一时间获取更多技术学习文章 udp是一个基于无连接的通讯协议 xff0c 通讯基本模型如下 可以看出 xff0c 不论是在客户端
  • c语言和c++的相互调用

    本文收录于微信公众号 LinuxOK xff0c ID为 xff1a Linux ok xff0c 关注公众号第一时间获取更多技术学习文章 在实际项目开发中 xff0c c和c 43 43 代码的相互调用是常见的 xff0c c 43 43
  • MSVC 版本号对应

    MSVC 43 43 14 0 MSC VER 61 61 1900 Visual Studio 2015 MSVC 43 43 12 0 MSC VER 61 61 1800 Visual Studio 2013 MSVC 43 43 1
  • SPI通讯协议介绍

    来到SPI通讯协议了 废话两句 xff0c SPI很重要 xff0c 这是我在学校时候听那些单片机开发工程师说的 出来实习 xff0c 到后来工作 xff0c 确实如此 xff0c SPI的使用很常见 xff0c 那么自然重要咯 SPI S
  • Qt多线程中的信号与槽

    1 Qt对象的依附性和事务循环 QThread继承自QObject xff0c 自然拥有发射信号 定义槽函数的能力 QThread默认声明了以下几个关键信号 信号只能声明不能定义 xff1a 1 线程开始运行时发射的信号 span clas