Qt中moveToThread多线程方法剖析

1,003 阅读6分钟

在QT中多线程的使用方法一共有两种:

先说第一种,继承自QThread类,然后重写虚函数run(),将耗时的操作写到run函数中,从而实现多线程操作,最后只需要在主函数中使用线程的start()函数将线程开启即可,使用起来比较简单,需要注意的是,开启线程时不需要调用run函数,使用线程的start函数即可。这种方法在新建类的时候需要继承自QThread,操作如下图所示。首先点击QT的添加文件按钮,弹出如下界面,选择c++,在右边选择C++ Class,然后点击右下角的Choose。

image.png

在弹出的界面中填写类的名字,此处我写的是firstmethod,最重要的是"Base class"这个地方,选择< Custom >,然后在下面的文本框内填写QThread,注意这里不要写错,要跟头文件内的QThread保持大小写一致才可以。

image.png

对于此类方法非常简单,此处不通过代码进行详细的说明。

对于moveToThread方法的实现是本文的重点,首先我们需要理解moveToThread的工作原理,这样才能够正确的使用多线程,否则程序虽然跑起来了,但很可能多线程并没有起到作用。

moveToThread形式的多线程实现方法需要将耗时操作实例化为槽函数,将这个槽函数所在的类推入Thread
主线程中调用槽函数实现多线程调度

对,就是上面这段话,如果理解错了,很可能多线程就起不到作用,导致看似是主线程在进行任务调度,实际上子线程没有起到作用。

目前网上有许多的说法,都指向了moveToThread方式的多线程,其实使用该方法时,QT新建类可以继承于QObject也可以继承于QThread,至少我是这么认为的,在本文针对上述我提到的两种方法我也做了实现,感觉没什么问题,继承于QThread时自己新建一个槽函数而不重写run方法,同样可以使用该多线程方式。

首先我把代码的目录结构放一下

image.png

这里没有用到界面,所以form里面的UI文件没有做任何修改,直接是新建项目时选择QWidget的默认界面,.pro文件同样没有做任何修改,直接默认即可,这里就不贴出此部分的代码浪费空间了。

然后下面是项目整体的头文件与cpp文件,代码的第一行注释为对应的文件名字

// mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
#include <QString>
#include <QDateTime>
#include <QDebug>
#include <QTime>
/*
    说明:耗时的操作放在此类中执行
    原因:moveToThread形式的多线程实现方法需要将耗时操作实例化为槽函数,将这个槽函数所在的类推入Thread
        主线程中调用槽函数实现多线程调度
*/
class MyThread : public QThread
{
public:
    MyThread();
public slots:
    void mySlots();
};
#endif // MYTHREAD_H
// mythread.cpp
#include "mythread.h"
MyThread::MyThread()
{
}
void MyThread::mySlots()
{
    //  qDebug()<<"child thread ID:" << QThread::currentThread()<<"current datetime:" << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
    // 此处写while的原因是表示较为耗时的操作
    while(1){
        qDebug()<<"while true!";
        qDebug()<<"child thread ID:" << QThread::currentThread();
        sleep(1);
    }
}
// sencondmethod.h ,此处新建文件时拼错了,多打了一个n,本想写secondmethod,新建文件时可以修正,后面引用头文件和实例化对象统一修改即可
#ifndef SENCONDMETHOD_H
#define SENCONDMETHOD_H
#include <QObject>
#include <QDebug>
#include <QThread>
#include <QDateTime>
#include <QTime>
class sencondmethod : public QObject
{
    Q_OBJECT
public:
    explicit sencondmethod(QObject *parent = nullptr);
signals:
public slots:
    void qDebugDateTime();
};
#endif // SENCONDMETHOD_H
// sencondmethod.cpp
#include "sencondmethod.h"
sencondmethod::sencondmethod(QObject *parent) : QObject(parent)
{
}
void sencondmethod::qDebugDateTime()
{
    //  qDebug()<<"secondmethod ID:"<<QThread::currentThread()<<"current datetime:"<<QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
    while(1){
        qDebug()<<"second while true!";
        qDebug()<<"child thread ID:" << QThread::currentThread();
        QThread::sleep(1);
    }
}
// widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include "mythread.h"
#include "sencondmethod.h"
#include <QWidget>
#include <QTimer>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    // 定时器,主要是给各个线程定时用的
    QTimer *childThreadTimer;
    QTimer *parentThreadTimer;
    QTimer *secondmethodTimer;
    
    MyThread *mythread;
    QThread *childthread;
    sencondmethod *second;
    QThread *secondthread;
public slots:
    void printTime();
private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
// widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 实例化三个定时器
    childThreadTimer = new QTimer(this);
    parentThreadTimer = new QTimer(this);
    secondmethodTimer = new QTimer(this);
    // 主线程操作,定时1秒指定槽函数printTime
    connect(parentThreadTimer, &QTimer::timeout, this, &Widget::printTime);
    parentThreadTimer->start(1000);
    // 首先利用QThread类实例化一个对象
    childthread = new QThread();
    // 开启线程对象
    childthread->start();
    // 将自己的类进行实例化,这个类继承自QThread
    mythread = new MyThread();
    // 将槽函数所在的类推入到Thread中
    mythread->moveToThread(childthread);
    // 连接槽函数,使用定时器进行操作
    connect(childThreadTimer,&QTimer::timeout, mythread, &MyThread::mySlots);
    childThreadTimer->start(1000);
    
    // 将自己的类进行实例化,这个类继承自QObject
    secondthread = new QThread();
    secondthread->start();
    second = new sencondmethod();
    second->moveToThread(secondthread);
    connect(secondmethodTimer, &QTimer::timeout,second, &sencondmethod::qDebugDateTime);
    secondmethodTimer->start(1000);
}
Widget::~Widget()
{
    delete ui;
}
void Widget::printTime()
{
    qDebug()<<"parent thread ID:" << QThread::currentThread()<<"current time" << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
}
// main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

上述操作执行时打印结果如下:

while true!
second while true!
parent thread ID: QThread(0x1f726ce0) current time "2022-07-11 10:30:08"
child thread ID: QThread(0x1f6241d0)
child thread ID: QThread(0x1f6240b0)
parent thread ID: QThread(0x1f726ce0) current time "2022-07-11 10:30:09"
second while true!
while true!
child thread ID: QThread(0x1f6240b0)
child thread ID: QThread(0x1f6241d0)
parent thread ID: QThread(0x1f726ce0) current time "2022-07-11 10:30:10"
while true!
second while true!
child thread ID: QThread(0x1f6241d0)
child thread ID: QThread(0x1f6240b0)
parent thread ID: QThread(0x1f726ce0) current time "2022-07-11 10:30:11"
second while true!
while true!
child thread ID: QThread(0x1f6240b0)
child thread ID: QThread(0x1f6241d0)
parent thread ID: QThread(0x1f726ce0) current time "2022-07-11 10:30:12"
second while true!
while true!
child thread ID: QThread(0x1f6240b0)
child thread ID: QThread(0x1f6241d0)
parent thread ID: QThread(0x1f726ce0) current time "2022-07-11 10:30:13"
second while true!
while true!
child thread ID: QThread(0x1f6240b0)
child thread ID: QThread(0x1f6241d0)
parent thread ID: QThread(0x1f726ce0) current time "2022-07-11 10:30:14"
while true!
second while true!
child thread ID: QThread(0x1f6241d0)
child thread ID: QThread(0x1f6240b0)
parent thread ID: QThread(0x1f726ce0) current time "2022-07-11 10:30:15"
second while true!
while true!
child thread ID: QThread(0x1f6240b0)
child thread ID: QThread(0x1f6241d0)
parent thread ID: QThread(0x1f726ce0) current time "2022-07-11 10:30:16"
while true!
second while true!
child thread ID: QThread(0x1f6241d0)
child thread ID: QThread(0x1f6240b0)
parent thread ID: QThread(0x1f726ce0) current time "2022-07-11 10:30:17"
second while true!
while true!
child thread ID: QThread(0x1f6240b0)
child thread ID: QThread(0x1f6241d0)
parent thread ID: QThread(0x1f726ce0) current time "2022-07-11 10:30:18"
while true!
second while true!
child thread ID: QThread(0x1f6241d0)
child thread ID: QThread(0x1f6240b0)

从结果中可以看到,程序一共开启了三个线程,每个线程都有自己独立的ID以区别于其它线程,两个子线程的while循环中我分别加入了一个休眠函数,以防止打印太快难以看清楚每一个线程调用操作,实验时为了可以查看每一个线程的操作,可以在子线程的while中加上打印当前时间的操作,同时也可以通过修改线程休眠的时间查看线程的执行结果。

之前的博客中对moveToThread方式的多线程有错误的理解,将定时器与类分别推入到了Thread中,其结果导致了看似线程在运行,而可能是掩盖迷障的结果,因此特别的将之前存在的问题进行修正,同时将正确的使用方式进行分享。