Qt跨线程发送信号与元数据

原创 2014年09月19日 10:46:48

        转载请注明出处:http://blog.csdn.net/luotuo44/article/details/39395025



        Qt的signals/slots是可以用在线程间的。由于事件循环(event loop)是在主线程完成的,所以在非主线程发送一个信号时,对应的槽函数将会由主线程执行。



        熟悉多线程的读者应该都感受到这里会有一个微妙的问题。如果signals/slots的函数参数是一个自己定义的类型。比如自己定义了一个Student类,信号函数为sendStudent(const Student &stu); 对应的槽函数为:getStudent(const Student &stu); 如果在非主线程使用emit发射信号的时候,Student参数是一个临时变量的话(即可能马上被析构掉),那么主线程在执行这个槽函数的时候这个临时变量可能被析构了。这就相当于使用了野指针。

 

        Qt的作者肯定也想到了这一点。

        我们回过头来深入了解connect函数,因为是它把信号和槽连起来的。在connect函数中,我们一般都只使用4个参数。实际上它是有5个参数的,只是使用了默认参数而已。第5个参数是一个枚举类型Qt::ConnectionType,有下面5种:

  • Qt::AutoConnection: 如果发射信号的线程和执行槽函数的线程是在同一个线程,此时等同于Qt::DirectConnection。如果不在同一个线程,就等同于Qt::QueuedConnection。这个是connect函数的默认参数
  • Qt::DirectConnection: 发射信号和执行槽是由同一个线程完成。此时槽函数马上被执行。执行完毕后才执行”emit 信号”后面的代码。即”emit 信号”是阻塞的
  • Qt::QueuedConnection: 发射信号的线程和执行槽函数的线程不是在同一个线程。此时发射信号的线程不阻塞,马上返回。当执行槽函数的线程被CPU调度时,就会执行槽函数
  • Qt::BlockingQueuedConnection: 和Qt::QueuedConnection基本一样,只是发射信号的线程会被阻塞,直到槽函数被执行完毕。所以如果设置了这个connect属性,那么就要确保发射信号线程不是执行槽函数的线程。否则将发生死锁
  • Qt::UniqueConnection: 唯一关联,该类型可以和上面的类型通过|符号配合使用。同一个信号与同一个槽只能调用connect一次。不能多次调用。注意,此时一个信号还是可以关联多个槽的

        上面枚举中,多次提及"是否在同一个线程"。其判断也简单,执行槽函数的线程是执行事件循环的线程,即执行QCoreApplication a(argc, argv); a.exec()函数的线程。一般都是主线程执行事件循环。发射线程也就是调用emit的线程。

 

        从上面的关联类型可以知道:Qt的作者是有考虑到发射信号的线程不是执行槽函数的线程。那么Qt是怎么解决刚才那个微妙的问题的呢?答案是元数据。在调用connect的前面调用qRegisterMetaType<Student>("Student");把这个Student类型注册成元数据。这样就能避免那个问题了。

 


 

        由于我没有阅读Qt的源代码,不知道Qt内部具体是怎么实现的。我把Qt的实现当作一个黑盒子,通过一些文档和测试进行一些猜想。下面就是我的猜想,不一定正确。


        假如这个微妙的问题给我们自己来解决,那么很容易想到的方法是:在内部复制一个Student类。这样无论发射线程的临时Student是否被析构都无所谓了。

        通过阅读Qt助手的qRegisterMetaType词条,会发现把一个类型注册成元数据是有条件的:这个类型要提供public属性的默认构造函数、复制构造函数、析构函数。这应该是为了复制一个Student吧。嘿嘿。下面通过一个例子验证之。

        头文件:

#ifndef TK_HPP
#define TK_HPP

#include<QString>
#include<QThread>
#include<QDebug>

class Student
{
public:
    Student(){}
    Student(const QString &name, const QString &id)
        :m_name(name), m_id(id)
    {}

    Student(const Student& stu)
    {
        //故意这样赋值。就是让Qt不能正确复制构造。哈哈!!!
        m_name = "xxxx";
    }

    QString name()const { return m_name; }
    void name(const QString& name) { m_name = name; }

private:
    QString m_name;
    QString m_id;
};


class Test : public QObject
{
    Q_OBJECT
public:
    Test()
    {
        qRegisterMetaType<Student>("Student");

        connect(this, SIGNAL(sendStu(Student)),
                this, SLOT(getStu(Student)));//, Qt::QueuedConnection );
    }

private slots:

    void getStu(const Student &stu)
    {
        qDebug()<<QThread::currentThreadId()<<" "<<stu.name();
    }

public:
    void printStu(const Student& stu)
    {
        emit sendStu(stu);
    }

private:
signals:
    void sendStu(const Student& stu);
};

class MyThread : public QThread
{
    Q_OBJECT

public:
    MyThread(Test * test) : m_test(test)
    {}

protected:
    void run()
    {
        qDebug()<<"non main thread "<<QThread::currentThreadId()<<'\n';
        Student stu("aaa", "213");

		 //发射信号
        m_test->printStu(stu);

        stu.name("bbb");

        qDebug()<<"I have reset student name\n";
    }

private:
    Test *m_test;
};

#endif // TK_HPP

        源文件:

#include <QCoreApplication>
#include"tk.hpp"


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

    MyThread mythread(&test);
    mythread.start();

    return a.exec();
}

        执行输出为:

        

        可以看到,Qt果然是复制错误了。



        如果删除Student中的复制构造函数(此时使用编译器提供的默认复制构造函数),那么输出就会为:

        

        可以看到,Qt内部已经复制了一份Student,所以即使次线程修改了Student的name,也不影响。


        如果在Test的构造函数中,删除qRegisterMetaType<Student>("Student")。那么在运行时候就会出现:

        


        估计Qt在emit的实现里面会判断发出信号的线程是否为主线程。如果不是的话,那么就检测signals/slots的参数是否为一个元数据。如果不是的话那么就拒绝发送信号。非元数据是不安全的。

        对于C/C++的基本类型和Qt定义的类,都不用我们手动将之注册为元数据了。Qt已经帮我们干了这些事情。

   



    

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

QThread与其他线程间相互通信,emit,发射信号

来自:http://blog.csdn.net/huihui1988/archive/2010/06/12/5665432.aspxQThread的用法其实比较简单,只需要派生一个QThread的子类...

Qt中信号与槽机制中emit的应用

信号与槽机制是qt的核心,一个信号可以接多个槽,多个信号也可以接一个槽,当然一个信号也可以接一个槽。本文的重点是讲述emit的应用,emit是qt中定义的宏,是用来发送一个信号,然后你再写一个槽函数。...

QT信号槽

信号槽是Qt中特有的概念。它使得程序员将不同的object绑定起来,而object对象间并不需要对相互了解。 Slots也是普通的c++方法,它们可以是virtual;可以被重载;可以使private...

QT子线程与主线程的信号槽通信

最近用QT做一个服务器,众所周知,QT的主线程必须保持畅通,才能刷新UI。所以,网络通信端采用新开线程的方式。在涉及到使用子线程更新Ui上的控件时遇到了点儿麻烦。网上提供了很多同一线程不同类间采用信号...

QT的signal和slot的几种常见使用场景

本文介绍了QT中的Signal和Slot使用的几种场景。

QT多线程通信EMIT

头文件: #ifndef HEAD_H #define HEAD_H #include #include #include #include #include cla...

Qt线程间的信号与槽 以及 QThread

问题描述:界面线程MainApp为主线程,工作线程MyThread为一子线程,从工作线程向主线程传递字符串用于在主线程中显示。 Qt的信号与槽机制可以将任何继承自QObject类的对象捆绑在一起...

Qt跨线程发送信号与元数据

转载请注明出处:http://blog.csdn.net/luotuo44/article/details/39395025         Qt的signa...

QT跨线程的信号与槽

QT程序是由主线程更新界面 若在主线程做一些耗时的操作,会导致界面暂时卡死 所以要把耗时的操作放到一个后台线程中去做 最好的方式是主线程发出信号,后台线程去干活 当后台线程干完活时发出信号,主线程进行...
  • Sidyhe
  • Sidyhe
  • 2015-08-19 14:41
  • 1679

Qt跨线程信号和槽的连接

Qt支持三种类型的信号-槽连接: 1,直接连接,当signal发射时,slot立即调用。此slot在发射signal的那个线程中被执行(不一定是接收对象生存的那个线程) 2,队列连接,当控制权回到对象...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)