qt 多线程编程信号与槽

本文详细探讨了Qt中的信号与槽机制在多线程环境下的使用,包括连接方式、线程间通信及线程依附关系调整。通过示例代码展示了如何在自定义线程类中发送信号并在主线程中处理槽函数,以及如何通过moveToThread改变对象的线程依附性,确保槽函数在线程中正确执行。内容涵盖了Qt线程管理、信号与槽的同步与异步调用、线程间通信的实现以及事件循环在信号与槽中的作用。
摘要由CSDN通过智能技术生成

目录

1、信号与槽的连接方式

2、多线程信号与槽

3、信号与槽的调用线程?

4、调整信号与槽所在线程的依附关系


      QThread类提供不依赖于平台的管理线程的方法。一个QThread类的对象管理一个线程,一 般从QThread继承一个自定义类,并重定义虚函数run(),在run()函数里实现线程需要完成的任务。

1、信号与槽的连接方式

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

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相同,同样能自动确定连接类型,但是加了限制:同一个信号和同一个槽函数之间只能有一个连接。

2、多线程信号与槽

        自定义信号与槽。MyClass类关联ThreadTest类信号函数,ThreadTest类实现线程创建,run()函数中发出信号,类内接收。示例1下面代码中:

1)ThreadTest线程类,通过run()函数不断产生数据,产生后通过自身定义的

        信号:counter_reset()、counter(m_counter)发送信号;

        槽函数:getCounter(int n)、on_counter_reset()接收信号并显示。

2)MyClass类,定义槽函数getstarted()、getfinished()接收ThreadTest线程类的started()和finished()信号。

// myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H
 
#include <QObject>
#include <QDebug>
 
class MyClass : public QObject
{
    Q_OBJECT
public:
    MyClass(){}
 
public slots:
    void getstarted()
    {
        qDebug()<<objectName()<<" : getstarted();"
    }
 
    void getfinished()
    {
        qDebug()<<objectName()<" : getfinished()";
    }
};
 
#endif // MYCLASS_H
 
// threadtest.h
#ifndef THREADTEST_H
#define THREADTEST_H
 
#include <QThread>
#include <QDebug>
 
class ThreadTest: public QThread
{
    Q_OBJECT
public:
    ThreadTest()
    {
        connect(this, SIGNAL(counter(int)), this, SLOT(getCounter(int)));
        connect(this, SIGNAL(counter_reset()), this, SLOT(on_counter_reset()));
    }
 
    void run()
    {
        int m_counter = 0;
        for(int i=0; i<10; i++)
        {
            if(0==i%5)
            {
                m_counter = 0;
                emit counter_reset();
            }
 
            emit counter(m_counter);
            m_counter = m_counter + 1;
            msleep(200);
        }
    }
 
signals:
    void counter(int n);
    void counter_reset();
 
public slots:
    void getCounter(int n)
    {
        qDebug()<< "getCounter : "<<n;
    }
 
    void on_counter_reset()
    {
        qDebug()<<" counter reset now !";
    }
};
 
#endif // THREADTEST_H

main.cpp函数调用:

#include <QCoreApplication>
#include <QObject>
#include <QDebug>
#include <QThread>
#include myclass.h
#include threadtest.h
 
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    ThreadTest threada;
    MyClass my;
    my.setObjectName(my);
 
    QObject::connect(&threada, SIGNAL(started()), &my, SLOT(getstarted()));
    QObject::connect(&threada, SIGNAL(finished()), &my, SLOT(getfinished()));
    threada.start();
    return a.exec();
}

运行结果:

        通过线程类中run()函数产生数据,使用信号与槽发送结果,主线程处理类中接收信号,显示结果。信号与槽机制实现了线程类向主线程类数据的主动通知。

3、信号与槽的调用线程?

        一个应用程序进程中会有一个主线程,一个进程中可以有多个线程,线程拥有独立的栈空间,而栈空间专门用于函数调用(比如保存函数参数,局部变量等)。

        如果一个函数的函数体中没有访问临界资源的代码,那么这个函数可以被多个线程同时调用,不会产生任何副作用。因为线程调用函数的时候用的是自己的栈空间,既然线程的栈空间是独立的,那么谁调用函数就用谁的栈空间,互不干扰。

        所以问题来了,槽函数本质也是个函数,那么槽函数是谁调用的呢?是主线程还是定义槽函数的线程还是其他线程?

        一般来说,我们开启一个线程,那么这个线程应该会有自己的线程id。为了知道当前线程的id,QThread提供currentThreadId()方法返回当前的线程ID,函数声明如下:

static Qt::HANDLE currentThreadId() Q_DECL_NOTHROW Q_DECL_PURE_FUNCTION;

        添加线程ID打印,测试:

// myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H

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

class MyClass : public QObject
{
    Q_OBJECT
public:
    MyClass(){}

public slots:
    void getstarted()
    {
        qDebug()<<objectName()<<":"<<"getstarted() , tid :"<<QThread::currentThreadId();
    }

    void getfinished()
    {
        qDebug()<<objectName()<<":"<<"getfinished(), tid :"<<QThread::currentThreadId();
    }
};
#endif // MYCLASS_H

// threadtest.h
#ifndef THREADTEST_H
#define THREADTEST_H

#include <QThread>
#include <QDebug>

class ThreadTest: public QThread
{
    Q_OBJECT
public:
    ThreadTest()
    {
        connect(this, SIGNAL(counter(int)), this, SLOT(getCounter(int)));
        connect(this, SIGNAL(counter_reset()), this, SLOT(on_counter_reset()));
    }

    void run()
    {
        qDebug()<<"ThreadTest tid "<<currentThreadId();
        int m_counter = 0;
        for(int i=0; i<10; i++)
        {
            if(0==i%5)
            {
                m_counter = 0;
                emit counter_reset();
            }

            emit counter(m_counter);
            m_counter = m_counter + 1;
            msleep(200);
        }
    }

signals:
    void counter(int n);
    void counter_reset();

public slots:
    void getCounter(int n)
    {
        qDebug()<< " tid:"<<currentThreadId()<< ", getCounter : " <<n;
    }

    void on_counter_reset()
    {
        qDebug()<<" tid:"<<currentThreadId()<< "counter reset now !";
    }
};

#endif // THREADTEST_H

main.cpp函数调用:

#include <QCoreApplication>
#include <QObject>
#include <QDebug>
#include <QThread>
#include "myclass.h"
#include "threadtest.h"
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    qDebug()<<"main tid:"<< QThread::currentThreadId();

    ThreadTest threada;
    MyClass my;

    my.setObjectName("my");

    QObject::connect(&threada,SIGNAL(started()),&my,SLOT(getstarted()));
    QObject::connect(&threada,SIGNAL(finished()),&my,SLOT(getfinished()));

    threada.start(); //开启线程执行函数
    qDebug()<<"main end";

    return a.exec();
}

运行结果:

         从运行结果看,我们的槽函数好像并不是在我们开启的线程中调用的呢,而是在主线程中。槽函数在线程类中写的,却跑到主线程中调用了,这也太奇怪了?

        问题:槽函数执行时的所在线程和信号发送操作的所在线程并不是同一个,前者位于main线程中,后者位于子线程中。下面解决。

4、调整信号与槽所在线程的依附关系

        上面测试表明,即使槽函数是定义在线程类中,调用函数的却不一定是这个线程。当然这不是我们希望的,有什么办法让调用槽函数的线程是本线程吗?

在QT中我们应该要知道几个问题:

        1)对象依附于哪个线程;

        2)对象的依附性与槽函数执行的关系;

        3)对象的依附性是否可以改变,如何改变。

        默认情况下,对象依附于自身被创建的线程。从代码中发现,是主线程创建了ThreadTest threada; 和 MyClass my;这两个对象,那么这两个对象就依附于主线程了。

        默认情况下,槽函数在对象所依附的线程中执行。所以就出现了上面情况。

        对象的依附性确实可以改变,从源码中我们可以找到这样一个函数:

//在 QObject 中 
void moveToThread(QThread *thread); // 该方法定义在了QObject中,通过这个方法可以改变对象的依附性。

测试代码(最终版):

在main函数中添加:

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    qDebug()<< "main tid: " << QThread::currentThreadId();
    ThreadTest threada;
    MyClass my;
    my.setObjectName("myClass");

    QObject::connect(&threada,SIGNAL(started()),&my,SLOT(getstarted()));
    QObject::connect(&threada,SIGNAL(finished()),&my,SLOT(getfinished()));

    // 改变对象依附
    threada.moveToThread(&threada);
    my.moveToThread(&threada);

    threada.start();
    qDebug()<< "main end";
    return a.exec();
}

结果:

        我们发送信号后,信号进入了事件队列里了。但是只是进入到事件队列却不去处理的话并没什么用,这就好比顾客点了菜,却没有人告诉厨师要做什么菜。调用exec();开启事件循环后就可以处理消息队列的消息了。

        信号与槽需要事件循环来支持,因为Qt4.4之后run()默认调用 QThread::exec() ,开启了事件循环,所以我们不主动在子线程中调用 exec()也能正常使用信号与槽。在4.4之前(包括4.4)我们需要自己手动开启事件循环,也就是调用QThread::exec() ,这样才能正常使用信号与槽。

结论:

        即使槽函数是定义在线程类中,调用函数的却不一定是这个线程。默认情况下,对象依附于自身被创建的线程。对象的依附性确实可以使用moveToThread函数改变。

为什么要改变对象的依附性?改变依附性有什么意义吗?

        前面说到多线程的时候强调过,只要用到了多线程,就可能产生临界资源竞争的情况。如果信号的发送和对应槽函数的执行在不同线程时,可能产生临界资源的竞争

传送门qt多线程系列文章目录

  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jingbo1801

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值