首页 > 编程语言 > 详细

第79课 多线程中的信号与槽(中)

时间:2017-03-01 22:32:15      阅读:250      评论:0      收藏:0      [点我收藏+]

1. 对象的依附性

(1)默认情况下对象依附于自身被创建的线程;例如,对象在主线程创建,则依附于主线程。

int main(int argc, char* argv[])
{
    //...
    MyThread t; /* t依附于主线程*/
    MyObject m; /* m依附于主线程*/
    //...
}

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

  ①如果采用Qt:: DirectConnection(即直接连接),表示直接让发射信号的线程去执行槽函数。这时无需关心接收者的依附性。

  ②如果采用Qt::QueuedConnection(即队列连接),信号将被投递到接收者所依附线程,所以槽函数由接收者依附的线程执行

  ③默认连接(Qt::AutoConnection):发送和接收同一线程,采用直接连接,槽函数由发送线程执行,当然也就等于接收者线程在执行,所以可以看接收者的依附性。如果不同线程间,会采用队列发送,信号须发送到接收者线程,所以仍然可以看接收者的依附性。(注意,同线程之间也可显式指定为队列连接不同线程间同样可以显式指定为直接连接;只是默认情况下,同线程间采用的是直接连接,不同线程间采用的是队列连接

  ④综上,如果指定为Qt:: DirectConnection则槽函数由发送者线程执行除此之外其他情况(含Auto和queue连接),槽函数均由接收者所依附线程执行

(3)对象的依附性是可以改变的QObject::moveToThread用于改变线程的依附性。

【编验实验】对象的依附性

//MyObject.h

#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <QObject>

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

protected slots:
    void getStarted();
    void testSlot();
};

#endif // MYOBJECT_H

//MyObject.cpp

#include "MyObject.h"
#include <QThread>
#include <QDebug>

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

}

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

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

//TestThread.h

#ifndef TESTTHREAD_H
#define TESTTHREAD_H

#include <QThread>

class TestThread : public QThread
{
    Q_OBJECT
protected:
    void run();
public:
    explicit TestThread(QObject* parent = 0);

signals:
    void testSignal();
protected slots:
    void testSlot();
};

#endif // TESTTHREAD_H

//TestThread.cpp

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

TestThread::TestThread(QObject *parent): QThread(parent)
{
    //由run中的emit testSignal可看出,子线程发出信号,接收信号由t对象完成
    //而在本例中t对象的可能分别依附于:
    //(1)测试1中:t对象依附于主线程。两者属不同的线程,所以采用队列连接,信号发往主线程
    //的事件队列,槽函数由主线程处理。
    //(2)测试2中:t对象依附于子线程,两者属同一线程,采用直接连接,槽函数由子线程自己处理。
    connect(this, SIGNAL(testSignal()), this, SLOT(testSlot()));//默认连接,也可由t的依附性
                                                                //看出槽函数的执行线程
}

void TestThread::run()
{
    qDebug() << "void TestThread::run() —— begin tid = " << currentThreadId();
    for(int i=0; i<5; i++){
        qDebug() << "void TestThread::run() i = " << i;
        sleep(1);
    }

    emit testSignal(); //子线程发出信号

    qDebug() << "void TestThread::run() —— end";
}

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

//main.cpp

#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QMutex>
#include "TestThread.h"
#include "MyObject.h"

//自定义消息处理程序,让QDebug输出是线程安全的。
void messageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    QByteArray localMsg = msg.toLocal8Bit();

    //fcntl(stderr, );

    static QMutex mutex;

    mutex.lock();
    switch (type) {
    case QtDebugMsg:
        fprintf(stderr, "%s\n", localMsg.constData());
        break;
    case QtInfoMsg:
        fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtWarningMsg:
        fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtCriticalMsg:
        fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtFatalMsg:
        fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        abort();
    }

    mutex.unlock();
}

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

    // 安装消息处理程序
    qInstallMessageHandler(messageOutput);

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

    TestThread t;
    MyObject m;

/*
    //测试1
    //started和testSignal信号都是由子线程发出,m对象(依附于主线程)接收信号。因两者属不同线程
    //默认会采用队列连接,信号被投递到m对象所依附的主线程,即两个槽函数都将在主线程中执行!
    QObject::connect(&t, SIGNAL(started()), &m, SLOT(getStarted())); //默认连接,也可按m的依附性判断执行于主线程
    QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()));//同上

    t.start();
*/

    //测试2
    //子线程发出started,同时本例中testSignal信号也是由子线程发出的,m对象(依附于子线程)接收信号。因收发属同一个线程
    //默认采用直接连接,即槽函数在子线程中执行!
    QObject::connect(&t, SIGNAL(started()), &m, SLOT(getStarted())); //默认连接,也可按m的依附性判断执行于子线程
    QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()));//同上
    m.moveToThread(&t);    //改变m的依附性为子线程
    //t.moveToThread(&t); //会影响TestThread::testSlot()的执行线程

    t.start();

    return a.exec();
}
/*输出结果:
 测试1:
main() tid =  0x1fcc //主线程
void TestThread::run() —— begin tid =  0x278c //子线程
void MyObject::getStarted() tid =  0x1fcc //m依附于主线程
void TestThread::run() i =  0
void TestThread::run() i =  1
void TestThread::run() i =  2
void TestThread::run() i =  3
void TestThread::run() i =  4
void TestThread::testSlot() tid =  0x1fcc //t依附于主线程
void TestThread::run() —— end
MyObject::testSlot() tid =  0x1fcc        //m依附于主线程

 测试2
main() tid =  0x95c  //主线程
void MyObject::getStarted() tid =  0x5c //m依附于子线程
void TestThread::run() —— begin tid =  0x5c
void TestThread::run() i =  0
void TestThread::run() i =  1
void TestThread::run() i =  2
void TestThread::run() i =  3
void TestThread::run() i =  4
MyObject::testSlot() tid =  0x5c         //m依附于子线程
void TestThread::testSlot() tid =  0x95c //t依附于主线程
void TestThread::run() —— end
*/

(4)研究执行槽函数线程的意义

  当信号的发送与对应槽函数的执行是在不同的线程中时,可能产生临界资源的竞争问题。如同一个线程类中的run()槽函数可能就会被不同的线程执行,这时可能出现上述问题。

2. 线程中的事件循环

技术分享 

(1)采用队列连接时,信号与槽的机制需要事件循环的支持。

(2)QThread类中提供的exec()函数用于开启线程的事件循环

(3)发往事件队列的信号会在事件循环中被特定的槽函数接收。只有开启了事件循环,对象中的槽函数才会在依附中线程中被调用

【编程实验】线程的事件循环

//MyObject.h(同上例)

技术分享
#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <QObject>

class MyObject : public QObject
{
    Q_OBJECT
public:
    explicit MyObject(QObject *parent = 0);
    
signals:
    
protected slots:
    void testSlot();
};

#endif // MYOBJECT_H
View Code

//MyObject.cpp(同上例)

技术分享
#include "MyObject.h"
#include <QThread>
#include <QDebug>

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

void MyObject::testSlot()
{
    qDebug() << "void MyObject::testSlot() tid = " << QThread::currentThreadId();
}
View Code

//TestThread.h

#ifndef TESTTHREAD_H
#define TESTTHREAD_H

#include <QThread>

class TestThread : public QThread
{
    Q_OBJECT

protected:
    void run();
public:
    explicit TestThread(QObject *parent = 0);
signals:
    void testSignal();
    void innerSignal();
};

#endif // TESTTHREAD_H

//TestThread.cpp

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

TestThread::TestThread(QObject *parent) :
    QThread(parent)
{
}

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

    emit innerSignal();
    exec(); //当信号和槽使用队列连接时,必须调用exec来开启事件循环。直接连接时则不必开启

    qDebug() << "void TestThread::run() -- end";
}

//main.cpp

#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QMutex>
#include "TestThread.h"
#include "MyObject.h"

/*演示多线程的信号和槽*/
//结论:当使用队列连接时,需要开启事件循环。直接连接时则不需要。

//自定义消息处理程序,让QDebug输出是线程安全的。
void messageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    QByteArray localMsg = msg.toLocal8Bit();

    //fcntl(stderr, );

    static QMutex mutex;

    mutex.lock();
    switch (type) {
    case QtDebugMsg:
        fprintf(stderr, "%s\n", localMsg.constData());
        break;
    case QtInfoMsg:
        fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtWarningMsg:
        fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtCriticalMsg:
        fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtFatalMsg:
        fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        abort();
    }

    mutex.unlock();
}

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

    // 安装消息处理程序
    qInstallMessageHandler(messageOutput);

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

    TestThread t;
    MyObject m;

    //测试1:收发都在主线程(默认连接,子线程无需开启事件循环)
    //QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()));
    //emit t.testSignal();


    //测试2:主线程发射,子线程接收(默认连接:队列连接,需要开启事件循环)
    //QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()));
    //m.moveToThread(&t);
    //emit t.testSignal();

    //测试3:主线程发射,子线程接收(改为直接连接,无需开启事件循环)
    //QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()),Qt::DirectConnection);
    //m.moveToThread(&t);
    //emit t.testSignal();

    //测试4:子线程发射,子线程接收(默认连接:直接连接,无需开启事件循环)
    //QObject::connect(&t, SIGNAL(innerSignal()), &m, SLOT(testSlot()));
    //m.moveToThread(&t);

    //测试5:子线程发射,子线程接收(改为队列连接,无需开启事件循环)
    QObject::connect(&t, SIGNAL(innerSignal()), &m, SLOT(testSlot()), Qt::QueuedConnection);
    m.moveToThread(&t);

    t.start();

    return a.exec();
}

3. 小结

(1)默认情况下:对象依附于自身被创建的线程,而对象中的槽函数在依附的线程中被调用执行

(2)QObject::moveToThread用于改变对象的线程依附性

(3)采用队列连接时,信号和槽机制需要事件循环的支持

(4)exec()函数用于开启线程的事件循环

第79课 多线程中的信号与槽(中)

原文:http://www.cnblogs.com/5iedu/p/6486518.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!