首页 > 代码库 > 第78课 多线程中的信号与槽(上)

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

1. QThread类中的信号和槽

(1)QThread类拥有发射信号定义槽函数的能力

技术分享 

(2)QThread中的关键信号

  ①void started():线程开始运行时发射该信号

  ②void finished():线程完成运行时发射该信号

  ③void terminated():线程被异常终止时发射该信号

2. 与线程相关的概念

(1)线程栈

  ①进程中存在栈空间的概念(区别于栈数据结构)

  ②栈空间专用于函数调用保存函数参数、局部变量等

  ③线程拥有独立的栈空间(可调用其它函数)

  ④只要函数体中没有访问临界资源的代码,同一个函数可以被多个线程同时调用,且不会产生任何副作用。

(2)进程PID和线程TID

  ①进程PID:进程拥有全局唯一的ID值。qint64 QProcess::processId() const

  ②线程TID:线程有进程内唯一的ID值。

(3)QThread中的关键静态成员函数

  ①Qt::HANDLE currentThreadId()

  ②QThread* currentThread()

3. 多个线程,槽函数是在哪个线程中执行的 

(1)自动连接:如果发射信号的线程槽函数对象所在线程同一线程,默认采用直接连接。如果位于不同线程,则采用队列连接

(2)直接连接(Direct Connection):说明槽函数要被立即、直接地调用可理解为采用send的方式)。所以,谁发射信号就由谁来接收信号(执行槽函数),即发射信号的线程即要发射信号,也要执行槽函数。

(3)队列连接(Queued Connection):说明信号被投递(post)到槽函数对象所在线程的事件队列中。因此理所当然地,槽函数应由其所在对象的线程执行

技术分享

【编程实验】槽函数的运行上下文多个线程,槽函数是在哪个线程中执行的?

//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 getStarted();    //响应线程开始的槽函数
    void getFinished();   //响应线程结束的槽函数
};

#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::getFinished()
{
    qDebug() << "void MyObject::getFinished() 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);
    ~TestThread();

signals:
    void testSignal();

protected slots:
    void testSlot();
};

#endif // TESTTHREAD_H

//TestThread.cpp

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

TestThread::TestThread(QObject *parent):QThread(parent)
{
    connect(this, SIGNAL(testSignal()), this, SLOT(testSlot()));
}

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

    for(int i=0; i<10; 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();
}

TestThread::~TestThread()
{
}

//main.cpp

#include <QCoreApplication>
#include <QThread>
#include <QMutex>
#include <QDebug>
#include "MyObject.h"
#include "TestThread.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;

    //m.moveToThread(&t); //这里可以取消注释来演示执行getStarted和getFinished()两个
                          //槽函数的线程的差别

    //默认连接:当m.moveToThread(&t)时,m与发射信号的子线程是同一线程,采用直接连接。
    //当不执行m.moveToThread时,发射信息线程与m所在线程不是同一线程,所以采用队列连接。
    //QObject::connect(&t, SIGNAL(started()), &m, SLOT(getStarted()));
    //QObject::connect(&t, SIGNAL(finished()), &m, SLOT(getFinished()));

    //队列连接:说明信号被投递到槽函数所在对象线程消息队列中的
    //本例中发射信号线程(子线程)将信号投到槽函数对象(m)所在线程
    QObject::connect(&t, SIGNAL(started()), &m, SLOT(getStarted()), Qt::QueuedConnection);

    //直接连接:说明槽函数是被直接调用的。所以由谁发射就由谁来接收(执行槽函数)。本例由子线程发射,也应由子线程接收)
    QObject::connect(&t, SIGNAL(finished()), &m, SLOT(getFinished()),Qt::DirectConnection);

    t.start();

    return a.exec();
}

/*输出结果:
main() tid =  0x428
void TestThread::run() -- begin tid =  0x2280
void MyObject::getStarted() tid =  0x428
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::run() i =  5
void TestThread::run() i =  6
void TestThread::run() i =  7
void TestThread::run() i =  8
void TestThread::run() i =  9
void TestThread::run() -- end
void TestThread::testSlot() tid =  0x428
void MyObject::getFinished() tid =  0x2280
*/

4. 小结

(1)QThread类拥有发射信号定义槽函数的能力

(2)线程在进程内拥有一个唯一的ID值

(3)线程拥有独立的栈空间用于函数调用

(4)没有临界资源的函数可以无副作用的被多个线程调用

(5)槽函数的调用在某一个线程中完成。

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