Qt线程的使用

2017-10-8 Plan C Qt

blog.kurukurumi.com原创,转载请注明出处。

E-Mail : hubenchang0515@outlook.com



零、Qt的事件循环与信号、槽

        

        Qt的事件循环会对事件产生响应,例如:按下键盘时调用keyPressEvent、点击鼠标时调用mousePressEvent等。

        除此之外事件循环里还会管理信号到槽的传递,加入信号A连接了槽B,信号A发送后,事件循环运行到信号槽处理的代码时就会调用槽B的函数。

        QApplicaion::exec即是所说的事件循环。

#include <QApplicaion>

int main(int argc, char* argv[])
{
    QApplication app(argc,argv);
    app.exec();
}

        每个QObject类对象的事件和信号槽均由所属线程的事件循环管理。通过QObject::moveToThread可更改其所属的线程。


一、QThread类的使用方法


        QThread类表示一个线程控制器,其本身属于主线程,QThread包含一个名为run的虚函数,这个函数运行在另一个线程中,QThread的功能就是控制该线程。如果没有重载run,run会调用exec函数从而产生一个运行在次线程中的事件循环。

        QThread是使用方法十分简单:

                1、继承QObject定义一个工作在次线程中的类。

                2、将该类实例化(不能给构造函数parent参数),并绑定信号槽。

                3、实例化一个QThread,通过moveToThread函数将该对象交给此线程管理,调用start函数启动线程。

        需要注意的一点是,如果GUI可以由多个线程控制,线程同步会十分困难,极易产生Bug,因此绝大多数GUI库均限制GUI只能由GUI线程(通常为主线程)控制。


        例如,编写一个MD5值计算器,由于MD5值的计算较为繁杂,对于较大的数据进行计算可能需要花费大量的时间,如果在主线程中进行计算,则会导致GUI失去响应,产生假死的现象,因此需要将MD5值的计算放大次线程中进行。

        1、继承QObject定义计算MD5值的类Md5Calculator

                这个类需要一个槽函数进行MD5值的计算,以及一个信号用于告知主线程计算出的MD5值,让主线程将其显示到GUI上。

#ifndef MD5CHECKER_H
#define MD5CHECKER_H

#include <QObject>

class Md5Calculator : public QObject
{
    Q_OBJECT
public:
    explicit Md5Calculator(QObject *parent = nullptr);

signals:
    /* 向主线程发送信号,由住线程来更新GUI */
    void finished(const QString& hex);  // MD5值计算完毕
    void message(const QString& msg); // 产生消息(希望主线程显示消息框)

public slots:
    void calculate(const QString& filename); // 计算file文件的MD5值
};

#endif // MD5CHECKER_H

                在槽函数中实现MD5值的计算,并在计算完毕后将MD5值通过信号发送给主线程。

#include "md5calculator.h"
#include <QFile>
#include <QCryptographicHash>

Md5Calculator::Md5Calculator(QObject *parent) : QObject(parent)
{

}

void Md5Calculator::calculate(const QString& filename)
{
    QFile file(filename);
    if(!file.open(QFile::ReadOnly))
    {
        /* 产生错误,发送信号,让住线程显示错误消息 */
        emit message(file.errorString());
        return;
    }

    /* 计算HD5值 */
    QCryptographicHash calculator(QCryptographicHash::Md5);
    calculator.reset();
    calculator.addData(&file);
    QString md5Value = calculator.result().toHex();
    file.close();

    /* 通过信号将MD5值发送给主线程 */
    emit finished(md5Value);
}


        2、将该类实例化,并绑定信号槽

                需要注意的一点是,因为之后需要将其交给次线程管理,因此不能有parent,即不能给予构造函数parent参数。

    Md5Calculator* md5c = new Md5Calculator;
    connect(this, &Md5Dialog::calculate, md5c, &Md5Calculator::calculate);
    connect(md5c, &Md5Calculator::finished, this, &Md5Dialog::showMd5);
    connect(md5c, &Md5Calculator::message, this, &Md5Dialog::showMessage);
    
    // Md5Dialog::calculate 是通知Md5Calculator开始计算的信号
    // Md5Dialog::showMd5 是显示MD5值的槽函数
    // Md5Dialog::showMessage 是显示消息框的槽函数


        3、实例化一个QThread,通过moveToThread函数将该对象交给此线程管理,调用start函数启动线程。

    QThread* md5Thread = new QThread(parent);
    md5c->moveToThread(md5Thread);
    md5Thread->start();

        由于对象md5c属于md5Thread管理的线程,因此其槽函数运行在次线程中。

        完整代码:https://github.com/hubenchang0515/Hash-Counter


        [注]在QObject::connect函数的最后一个参数为默认值时,槽函数由接收对象所属的事件循环调用。使用没有receiver参数的connect重载版本时,因为不存在接收对象,因此槽函数是由发送对象所属的事件循环调用的。

        即使用类的静态成员函数、普通函数以及lambda作为槽函数,其运行在发送者所属的线程中。

/* 没有接受者的connect */
QMetaObject::Connection QObject::connect(const QObject *sender,
                     PointerToMemberFunction signal,
                     Functor functor);


二、QThread不建议使用的另一种用法

        

        如上文所述,QThread包含一个虚函数run,该函数运行在次线程中,因此可以继承QThread并重载run函数来编写运行在次线程中的代码。但是这种方式较难控制因此不建议使用。

        在Qt4.4之前QThread是抽象类,run是纯虚函数,必须继承QThread,重载run来使用多线程。但从Qt4.4开始run函数会调用exec函数来产生一个运行在次线程的事件循环,因此不再需要使用这种方法。


三、与多线程相关的一些类


        QMutex是互斥量。

        QMutexLocker是简化互斥量使用的类,其构造函数需要一个互斥量作为参数,构造时锁定该互斥量,析构时解锁该互斥量。

        QReadWriteLock是读写锁,可以分别锁定读和写。

        QReadLocker、QWriteLocker分别锁定读写锁的读和写,构造时锁定,析构时解锁。

        QSemaphore是信号量。

发表评论:

Powered by emlog
鄂ICP备16003833号