Qt中槽函数在那个线程执行的探索和思考

news2024/9/22 17:22:40

      信号和槽是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();
}

运行效果如下:

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

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

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

相关文章

关于负载和驱动能力的问题总结

这两天重新接触到了驱动能力这个说法&#xff0c;之前也听过&#xff0c;但是一直不理解是怎么回事儿&#xff0c;也就没有深究&#xff0c;现在想来&#xff0c;这里面还是有点门道的。 驱动能力&#xff0c;说的是什么呢&#xff1f;应该就是带载能力&#xff0c;而带载能力&…

热烈庆祝安徽普朗膜技术有限公司参加2024济南生物发酵展

公司自2004年注册成立以来主要业务领域主要有以乳酸、氨基酸、抗生素为主的发酵液的提取分离&#xff1b;醋、酱油发酵产品的产品升级&#xff0c;果汁、茶饮料等天然产物提取的除菌和澄清过滤&#xff1b;低聚木糖、低聚果糖、果葡糖浆、高果糖浆等过滤、纯化、浓缩&#xff1…

RRC下的NAS层

无线资源控制&#xff08;Radio Resource Control&#xff0c;RRC&#xff09;&#xff0c;又称为无线资源管理&#xff08;RRM&#xff09;或者无线资源分配&#xff08;RRA&#xff09;&#xff0c;是指通过一定的策略和手段进行无线资源管理、控制和调度&#xff0c;在满足服…

D34|不同路径

62.不同路径 初始思路&#xff1a; 1&#xff09;确定dp数组以及下标的含义&#xff1a; dp[i][i]存放到第i1行和第i1列的方法数 2&#xff09;确定递推公式&#xff1a; dp[i][i] dp[i -1][i] dp[i][i-1] 3&#xff09;dp数组如何初始化 第0行是1&#xff1b; 第0列是1&a…

关于文件操作---C语言

引言 关于文件&#xff0c;想必大家或多或少都会有些了解&#xff0c;文件可以帮我们储存数据&#xff0c;不同格式的文件可以储存不同类型的数据&#xff0c;也可以将文件中的数据用不同的方式打开。电脑中的文件&#xff0c;是放在硬盘上的。在我们编写代码并运行的时候&…

KAKFA实践零碎记录

这里写目录标题 1 内存泄露2 生产者报错 1 内存泄露 错误信息 反复执行&#xff1a;创建消费者->关闭消费者后&#xff0c;内存缓慢上升且GC不能回收内存 错误原因 关闭消费者需要执行KafkaConsumer#close()函数 public void close() {this.close(Duration.ofMillis(30000…

12.12_黑马数据结构与算法笔记Java

目录 079 优先级队列 无序数组实现 080 优先级队列 有序数组实现 081 优先级队列 堆实现 1 082 优先级队列 堆实现 2 083 优先级队列 堆实现 3 084 优先级队列 e01 合并多个有序链表1 084 优先级队列 e01 合并多个有序链表2 085 阻塞队列 问题提出 086 阻塞队列 单锁实…

double DQN 跑 Pendulum-v1

gym-0.26.1 Pendulum-v1 环境详细信息 double DQN 实验环境 是为了体现 double DQN对高估的缓解, 因为 Pendulum-v1 reward最大是为0&#xff0c;可以有明显的对比。 相关论文 Deep Reinforcement Learning with Double Q-Learning 对动手深度强化学习里的代码做了一些修改。…

Java连接数据库实现用户登录和注册功能

目录 需求内容如下 示例代码 数据库studb Java代码 效果图 需求内容如下 1&#xff0c;创建数据库studb 2&#xff0c;库中添加用户表userinfo,包含如下字段 用户id ,用户名&#xff0c;用户密码&#xff0c;用户权限 &#xff08;数据类型和约束自己定义&#xff09…

SpringBoot2—开发实用篇2

目录 数据层解决方案 SQL NoSQL SpringBoot整合Redis SpringBoot整合MongoDB SpringBoot整合ES 数据层解决方案 SQL 回忆一下之前做SSMP整合的时候数据层解决方案涉及到了哪些技术&#xff1f;MySQL数据库与MyBatisPlus框架&#xff0c;后面又学了Druid数据源的配置&am…

TrustGeo代码理解(一)main.py

代码链接:https://github.com/ICDM-UESTC/TrustGeo 一、导入各种模块和数据库 # -*- coding: utf-8 -*- import torch.nnfrom lib.utils import * import argparse, os import numpy as np import random from lib.model import * import copy from thop import profile imp…

文本聚类——文本相似度(聚类算法基本概念)

一、文本相似度 1. 度量指标&#xff1a; 两个文本对象之间的相似度两个文本集合之间的相似度文本对象与集合之间的相似度 2. 样本间的相似度 基于距离的度量&#xff1a; 欧氏距离 曼哈顿距离 切比雪夫距离 闵可夫斯基距离 马氏距离 杰卡德距离 基于夹角余弦的度量 公式…

【从零开始学习JVM | 第七篇】深入了解 堆回收

前言&#xff1a; Java堆作为内存管理中最核心的一部分&#xff0c;承担着对象实例的存储和管理任务。堆内存的高效使用对于保障程序的性能和稳定性至关重要。因此&#xff0c;深入理解Java堆回收的原理、机制和优化策略&#xff0c;对于Java开发人员具有重要的意义。 本文旨在…

竞赛保研 python图像检索系统设计与实现

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; python图像检索系统设计与实现 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 该项目较为新颖&#xff0c…

Python基础04-数据容器

零、文章目录 Python基础04-数据容器 1、了解字符串 &#xff08;1&#xff09;字符串的定义 字符串是 Python 中最常用的数据类型。我们一般使用引号来创建字符串。创建字符串很简单&#xff0c;只要为变量分配一个值即可。<class ‘str’>即为字符串类型。一对引号…

计算机毕业设计 基于SpringBoot的二手物品交易管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

【一文带你掌握Java中方法定义、调用和重载的技巧】

方法的定义和调用 方法的定义 方法&#xff08;method&#xff09;是一段用于实现特定功能的代码块&#xff0c;类似于其他编程语言中的函数&#xff08;function&#xff09;。方法被用来定义类或类的实例的行为特征和功能实现。方法是类和对象行为特征的抽象表示。方法与面向…

『PyTorch』张量和函数之gather()函数

文章目录 PyTorch中的选择函数gather()函数 参考文献 PyTorch中的选择函数 gather()函数 import torch a torch.arange(1, 16).reshape(5, 3) """ result: a [[1, 2, 3],[4, 5, 6],[7, 8, 9],[10, 11, 12],[13, 14, 15]] """# 定义两个index…

圆通速递查询,圆通速递单号查询,一键复制查询好的物流信息

批量查询圆通速递单号的物流信息&#xff0c;并将查询好的物流信息一键复制出来。 所需工具&#xff1a; 一个【快递批量查询高手】软件 圆通速递单号若干 操作步骤&#xff1a; 步骤1&#xff1a;运行【快递批量查询高手】软件&#xff0c;第一次使用的朋友记得先注册&…

002.Java实现两数相加

题意 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示两数之和的新链表。 示例 输入&#xff1a;l1[2,4,3],l2[5,6,4] 输出…