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_OBJECT

public:
    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::QueuedConnection
    Qt::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_OBJECT

public:
    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_OBJECT

public:
    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();
}

运行效果如下:

       如上,发送者对象在主线程创建,在子线程中发送信号,槽函数是在主线程中执行,而并非在子线程中执行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/251556.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[BUG]TDA4 main域 CAN 无法进中断

目录 关键词平台说明一、背景二、根本原因2.1 Com模块 三、措施 关键词 嵌入式、C语言、autosar、TDA4 平台说明 项目ValueOSautosar OSautosar厂商vector芯片厂商TI编程语言C&#xff0c;C编译器HighTec (GCC) 一、背景 在将mcu域的部分can 移植到main域的时候发现无法进…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《考虑灵活性资源传输精细化建模的配电网优化运行》

这个标题表达的是关于配电网优化运行的一个概念&#xff0c;其中考虑了灵活性资源传输的精细化建模。让我们逐个解读关键词&#xff1a; 考虑灵活性资源传输&#xff1a;这指的是在配电网优化运行中考虑到不同类型的灵活性资源的传输。灵活性资源包括可再生能源、储能系统、柔性…

HarmonyOS首次尝试-HelloWorld

我的旧手机是个HUAWEI PCT-AL10 HarmonyOS 3.0.0(Android 10) 插上后&#xff0c;studio能显示连接上了手机设备&#xff0c;创建的demo使用的是API9&#xff0c;也就是当前的最新版本。 点击运行报错&#xff1a; 点击去往帮助页&#xff0c;做的也挺好&#xff0c;有直达的…

抠图软件哪个好用?什么软件可以抠图换背景?

抠图软件哪个好用&#xff1f;在图片处理中&#xff0c;抠图换背景是一项常见的操作。很多新手可能会对此感到困惑&#xff0c;不知道应该使用什么软件来进行抠图换景。实际上&#xff0c;现在市面上有很多图片处理软件都具备抠图换背景的功能&#xff0c;每款软件都有其优缺点…

开辟“护眼绿洲”,荣耀何以为师?

文 | 智能相对论 作者 | 佘凯文 俗话说&#xff0c;眼睛是心灵的窗户&#xff0c;可如今&#xff0c;人们对于这扇“窗户”的保护&#xff0c;似乎越来越不重视。 据人民日报今年发布的调查显示&#xff0c;中国眼病患病人数2.1亿&#xff0c;近视患者人数多达6亿&#xff0…

Python学习之爬虫基础

目录 文章声明⭐⭐⭐让我们开始今天的学习吧&#xff01;requests库的基本使用BeautifulSoup解析HTML我们还需要学习什么呢&#xff1f; 文章声明⭐⭐⭐ 该文章为我&#xff08;有编程语言基础&#xff0c;非编程小白&#xff09;的 Python爬虫自学笔记知识来源为 B站UP主&…

【JVM从入门到实战】(五)类加载器

一、什么是类加载器 类加载器&#xff08;ClassLoader&#xff09;是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术。 类加载器只参与加载过程中的字节码获取并加载到内存这一部分。 二、jdk8及之前的版本 类加载器分为三类&#xff1a; 启动类加载器-加载Ja…

2043杨辉三角(C语言)

目录 一&#xff1a;题目 二&#xff1a;思路分析 三&#xff1a;代码 一&#xff1a;题目 二&#xff1a;思路分析 1.通过杨辉三角&#xff0c;不难发现中间的数等于肩头两个数之和 2.但是当我们的输出结果&#xff0c;与杨辉三角的形式有所不同&#xff0c;但是我们可以找…

【Linux】高性能 Web 服务器 Nginx 安装教程(Ubuntu 22.04)

前言 Nginx 是一个高性能的开源 Web 服务器软件&#xff0c;也可以用作反向代理服务器、负载均衡器、HTTP 缓存以及作为邮件代理服务器等。Nginx 以其高性能、稳定性和丰富的功能而闻名&#xff0c;被广泛用于构建高流量网站和应用程序。 步骤 更新软件源 首先需要更新系统的软…

AttributeError: module ‘edge_tts‘ has no attribute ‘Communicate‘解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

pycdc配置和使用

配置 使用的kali2023版本 安装cmake pip install cmake 下载pycdc git clone https://github.com/zrax/pycdc 切换到pycdc目录 cd pycdc 进行cmake cmake CMakeLists.txt make 使用 显示帮助信息 ./pycdc -h 显示帮助信息 反编译 ./pycdc /home/kali/Desktop/main…

瑞萨单片机学习:RA4M3单片机 BOOTloader升级 跳转到主程序 主程序无法执行问题

背景&#xff1a; 使用瑞萨的RA4M3单片机编写BOOT引导程序进行测试&#xff0c;在BOOT程序跳转到主程序时&#xff0c;主程序无法执行。本文介绍了问题的定位和解决方法。 运行开发环境介绍 硬件环境 RA4M3 官方开发板 J-LINK V11 开发板自带 软件开发环境 e2 studio VSCODE…

【MySQL基础】MySQL基本操作详解

系列文章目录 第1篇&#xff1a;【MySQL基础】MySQL介绍及安装 第2篇&#xff1a;【MySQL基础】MySQL基本操作详解 文章目录 ✍1&#xff0c;数据库操作     &#x1f50d;1.1,查看数据库     &#x1f50d;1.2,创建数据库     &#x1f50d;1.3,选择数据库    …

探索太空深渊:计算机技术在航天领域的无限可能

探索太空深渊&#xff1a;计算机技术在航天领域的无限可能 一、引言 在21世纪的科技浪潮中&#xff0c;太空探索和计算机技术无疑是两个最为璀璨夺目的领域。它们各自的发展都足以改变人类社会的未来&#xff0c;而当这两者交汇时&#xff0c;所激发出的创新和变革更是超乎我…

算法通关第十九关-青铜挑战理解动态规划

大家好我是苏麟 , 今天聊聊动态规划 . 动态规划是最热门、最重要的算法思想之一&#xff0c;在面试中大量出现&#xff0c;而且题目整体都偏难一些对于大部人来说&#xff0c;最大的问题是不知道动态规划到底是怎么回事。很多人看教程等&#xff0c;都被里面的状态子问题、状态…

Windows 10如何关闭系统自动更新(实用教程)

本章教程&#xff0c;用最简洁的方式介绍在windows10中如何关闭系统自动更新。 目录 一、关闭自动更新服务 二、关闭自动更新组策略 一、关闭自动更新服务 1、 winr 2、services.msc 3、找到并双击 Windows Update 修改启动类型为禁用 二、关闭自动更新组策略 1、winr 2、gp…

社交网络分析2(下):社交网络情感分析的方法、挑战与前沿技术

社交网络分析2&#xff08;下&#xff09;&#xff1a;社交网络情感分析的方法、挑战与前沿技术 写在最前面7. 词嵌入&#xff08;word embedding&#xff09;的主要目的是什么&#xff1f;结合某方法简要地说明如何实现词嵌入。主要目的实现方法示例&#xff1a;GloVe案例分析…

python深拷贝和浅拷贝

文章目录 浅拷贝深拷贝 刷完这60个标准库模块&#xff0c;成为Python骨灰级玩家 深拷贝和浅拷贝都是用于复制对象的概念。浅拷贝在复制对象时&#xff0c;仅复制其引用&#xff0c;而非复制对象本身。这意味着原对象和新对象都指向相同的内存地址&#xff0c;修改一个对象会影…

linux 文本信息查询grep;控制命令执行和管道操作符号

1、grep grep "keyword" /path/to/logfile获取查询结果最后一行 grep "runs/detect/train" test4.log | tail -n 12、linux控制命令执行和管道操作符号 &、|、; 和 &&、》、>、< ##例子&#xff1b;wandb disabled && yolo …

new一个对象

1.自己直接调用 function Person(name, age) {this.name name;this.age age;}let a1 new Person("小明", 20);let a2 new Person("小菜", 25);console.log(a1); 打印的对象: 2.自己模拟一个 function Person(name, age) {this.name name;this.age a…