Qt之多线程---最好的教程,没有之一

· Qt 多线程发展史

  • version-4.6以前只能继承QThread来实现
  • version-4.6以后,官方推荐使用继承QObject 来实现

· 继承 QThread

第一步:先定义子线程类 MyThread 头文件:

这里写图片描述

这里写图片描述

MyThread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QObject>  //包含该文件,使用信号和槽
#include <QThread>  //添加头文件

class MyThread : public QThread  //注意修改基类,原先是继承QObject,改成 QThread!!!
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = 0);  //会自动生成

protected:
    //run() 是继承下来的虚函数
    // 是 protected 的函数
    //也是唯一的线程处理函数,注意是唯一
    //不能直接调用,正确姿势是通过start()间接调用
    void run(); 

signals:
    void isDone(); //线程运行后发出结束信号,只声明不实现

public slots:

};

#endif // MYTHREAD_H

MyThread.cpp

#include "MyThread.h"

MyThread::MyThread(QObject *parent) : QThread(parent)  //记得修改基类!!!
{

}

void MyThread::run(){
    //在这里执行很复杂的数据处理
    //假设,很复杂的数据处理需要耗时5s
    sleep(5);  //这是Qt的睡觉函数,以秒为单位 ,加 m 是毫秒,加 u 是微秒,C++的睡觉 S 要大写
    emit isDone(); //最后一刻再释放信号
}

第二步:接下来下一个窗口 Widget,实现的功能是:点击按钮,计时器开始计时,子线程开始运行复杂的操作,再次点击,子线程结束,计时器停止计时:

Widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTimer>
#include <MyThread.h>  //线程头文件

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

    void dealTimeout();  //定时器槽函数,定时器开始计时后要做些什么
    void dealDone();  //子线程结束槽函数,子线程结束后,要做些什么
    void stopThread();  //停止线程槽函数

private slots:
    void on_pushButton_clicked(); //窗口按钮的点击事件

private:
    Ui::Widget *ui;
    QTimer * myTimer;  //声明定时器,可以不适用指针,但是推荐使用
    MyThread *thread;  //声明子线程,可以不适用指针,但是推荐使用
};

#endif // WIDGET_H

Widget.cpp

#include "Widget.h"
#include "ui_Widget.h"
#include <QDebug>
#include <QThread>
#include <QDebug>  //该写的都写上

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    //实例化定时器,一定要在构造时候就实例化
    myTimer = new QTimer(this);

    //只要定时器启动。自动触发 timeout(),定时器关闭后则不执行
    connect(myTimer,&QTimer::timeout , this , &Widget::dealTimeout);

    //分配空间
    thread = new MyThread(this);

    //关联结束动作
    connect(thread, &MyThread::isDone, this, &Widget::dealDone);

    //关闭窗口右上角时,触发
    connect(this,&Widget::destroyed,this,&Widget::stopThread);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::dealTimeout(){
    static int i =0;
    i++;
    //设置lcd的值
    ui->lcdNumber->display(i);  //读者在窗口放一个 LCDNumber 控件,用于显示计时器走了多长时间
}

void Widget::dealDone(){
    qDebug()<< "it is over";
    myTimer->stop();  //关闭定时器,这里的 stop() 是 QTimer 自己的方法
}

void Widget::stopThread(){
    //thread->terminate(); //坚决不要用,涉及到动态分配内存就会出现内存泄漏问题
    thread->quit();  //这个会等到数据处理完,才退出
    thread->wait();  //等待结束(会出现阻塞,但是感觉不到),回收资源
}

void Widget::on_pushButton_clicked()
{
    //如果定时器没有工作,这个判断语句是良好的代码规范
    if(myTimer->isActive() == false){
        myTimer->start(100); //只执行100毫秒
    }

    //启动线程,处理数据
    //start() 间接调用 run() 函数
    thread->start();  
}

第三步:最后再看 main 函数

扫描二维码关注公众号,回复: 1545298 查看本文章

main.cpp

#include "Widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;  //实例化窗口
    w.show();   //展示出来

    return a.exec();
}

至此子线程的创建与停止就实现了,效果见下图:

这里写图片描述

这里写图片描述

使用继承 QThread 的注意事项:

1、修改 MyThread.h 和 MyThread.cpp 的继承方式(修改为 QObject)
2、run() 函数是唯一的处理复杂操作的函数,MyThread定义的其他函数仍在主线程执行
3、注意子线程的信号返回时机
4、调用线程的窗口在构造时就要实例化一个 MyThread *
5、没了


· 继承 QObject

第一步,还是先建立子线程类 MyThread

添加 C++ 类,注意选择:

这里写图片描述

MyThread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QObject>

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

    //没有继承 QThread,所以没有run() 函数
    //自定义线程处理函数,自己命名
    //同样的,也是唯一的线程处理函数,其他函数仍运行在主线程
    void myTimeout();

    //设置线程开始与结束的标记位,在后续会有用途讲解
    void setFlag(bool flag = true);

signals:
    void mySignal();  //这个是子线程运行结束后发出的信号

public slots:

private:
    //标志位,在后面会有讲解
    bool isStop;
};

#endif // MYTHREAD_H

MyThread.cpp

#include "MyThread.h"
#include <QThread>
#include <QDebug>
#include <QMessageBox>

MyThread::MyThread(QObject *parent) : QObject(parent)
{
    //线程一开始是要执行的,所以设置为 false ,注意逻辑
    isStop = false;
}

void MyThread::myTimeout(){
    while(isStop == false){  //如归线程在运行,才继续执行
        //在这里写复杂的操作
        QThread::sleep(1);  //这里是秒

        emit mySignal();  //每次执行都发送信号

        //线程函数内部绝对不允许操作图形界面,会报错
        //QMessageBox::aboutQt(NULL);

        qDebug()<<"子线程号 : "<<QThread::currentThread();

        if(true == isStop){ //如果主线程将isstop设置为true,则子线程停止执行,调出while循环
            break;
        }
    }
}

void MyThread::setFlag(bool flag){
    isStop = flag;
}

第二步:主窗口的写法

Widget.h

在窗口放置两个按钮,一个start,一个stop,然后还是放置一个 LCDNumber

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <MyThread.h>  //主要是为了使用它的函数
#include <QThread>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

    void dealSignal();   //处理来自子线程的信号
    void dealDestroy();  //处理关闭窗口事件,主要用来结束线程

private slots:
    void on_pushButton_clicked();    //start按钮的操作 --- 开始线程
    void on_pushButton_2_clicked();  //stop 按钮的操作 --- 结束线程

signals:
    void startThread();  //启动子线程的信号

private:
    Ui::Widget *ui;  //UI 界面
    //注意以下两者的区别
    MyThread *myT;    //线程对象
    QThread *thread;  //子线程
};

#endif // WIDGET_H

Widget.cpp

#include "Widget.h"
#include "ui_Widget.h"
#include <QDebug>
#include <Qt>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    //myT 分配空间,一定不能指定父对象!!!
    myT = new MyThread;  
    //myT = new MyThread(this);  

    //创建子线程
    thread = new QThread(this);  

    //把我自定义的线程加入到子线程中
    //如果 myT 指定了父对象,那么的话,就是说系统不允许你把有父亲的儿子移动到别的父亲
    myT->moveToThread(thread); //关键操作

    //运行中的子线程发出了信号,UI线程接收信号(用于更新界面UI,这里是更新的lcdNumber)
    connect(myT,&MyThread::mySignal,this,&Widget::dealSignal);

    //UI线程发出启动子线程信号,子线程接收到信号后开始执行复杂运算
    connect(this,&Widget::startThread,myT,&MyThread::myTimeout);  //第五个参数的使用 Qt::QueuedConnection(默认) 和 Qt::DirectConnection

    //UI线程发出的关闭窗口信号,UI线程做子线程善后工作
    connect(this,&Widget::destroyed,this,&Widget::dealDestroy);

    //打印UI线程句柄
    qDebug()<<"主线程号 : "<<QThread::currentThread();

}

Widget::~Widget()
{
    delete ui;
}

void Widget::dealSignal(){  //改变UI界面
    static int i=0;
    i++;
    ui->lcdNumber->display(i);
}

void Widget::dealDestroy(){
    on_pushButton_2_clicked();
    delete myT;  //不是必须的,但是不写可能会导致内存泄漏
}

void Widget::on_pushButton_clicked()
{
    //良好的代码写法
    if(thread->isRunning() == true)
        return;

    //启动线程,但是没有启动线程处理函数
    thread->start();
    myT->setFlag(false);

    //不能直接调用线程处理函数,直接调用导致线程处理函数和主线程在同一个线程
    //myT->myTimeout();

    //只能通过 signal - slot 调用
    emit startThread();  //这才是正确执行线程函数的姿势
}

void Widget::on_pushButton_2_clicked()
{
    if(thread->isRunning() == false)
        return;

    myT->setFlag(true);   //这种做法实际上停止不了线程,但是也要写,日后会知道好处的
    thread->quit();  //这样才是正确姿势
    thread->wait();
}

第三步:最后来看 main 函数

#include "Widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();

    return a.exec();
}

看看执行效果吧:

这里写图片描述

这里写图片描述

使用继承 QObject 的注意事项:

1、不用 MyThread.h 和 MyThread.cpp 的继承方式,因为它本身就继承 QObject
2、不需要实现 run() 函数,但是自己得写一个运行复杂操作的函数,并且它也是唯一的处理复杂操作的函数,MyThread定义的其他函数仍在主线程执行
3、注意子线程的信号返回时机
4、调用线程的窗口在构造时就要实例化一个 QThread * 和 MyThread *
5、涉及到 主线程释放信号 - 子线程处理子线程释放信号 - 主线程处理 的信号 - 槽机制


· 总结

· 复杂的操作一定要开辟多线程,完美解决复杂操作主界面卡死的情况,亦即我我们经常见到的程序未响应;
· 作为一名学生党,搞科研,处理“大数据”,多线程必不可少
· Qt 的多线程还是很方便的;
· 加油吧,站在鄙人失败的肩膀上继续向前~~~


· 联系作者

邮箱 : [email protected]

2018年4月14日 晚

PS:今晚 金州勇士 季后赛第一场对马刺,勇士加油 ~~~

猜你喜欢

转载自blog.csdn.net/qq_34719188/article/details/79945151