• QT_C++_多线程


    QT_C++_多线程

    前言:QThread 线程类是实现多线程的核心类。Qt 有两种多线程的方法,其中一种是继承 QThread的 run()函数,另外一种是把一个继承于 QObject 的类转移到一个 Thread 里。Qt4.8 之前都是使用继承 QThread 的 run()这种方法,但是 Qt4.8 之后,Qt 官方建议使用第二种方法。两种方法区别不大,用起来都比较方便,但继承 QObject 的方法更加灵活。所以 Qt 的帮助文档里给的参考是先给继承 QObject 的类,然后再给继承 QThread 的类。

    第一种办法继承 QThread的 run()函数

    mainwindow.h

    当线程执行完成时,会发送 resultReady(const QString &s)信号给主线程。重写 run()方法,这里很重要。把耗时操作写于此,本例相当于一个继承 QThread类线程模板了。

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    #include <QThread>
    #include <QDebug>
    #include <QPushButton>
    
    /* 使用下面声明的WorkerThread线程类 */
    class WorkerThread;
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        MainWindow(QWidget *parent = nullptr);
        ~MainWindow();
    
    private:
        /* 在MainWindow类里声明对象 */
        WorkerThread *workerThread;
    
        /* 声明一个按钮,使用此按钮点击后开启线程 */
        QPushButton *pushButton;
    
    private slots:
        /* 槽函数,用于接收线程发送的信号 */
        void handleResults(const QString &result);
    
        /* 点击按钮开启线程 */
        void pushButtonClicked();
    };
    
    /* 新建一个WorkerThread类继承于QThread */
    class WorkerThread : public QThread
    {
        /* 用到信号槽即需要此宏定义 */
        Q_OBJECT
    
    public:
        WorkerThread(QWidget *parent = nullptr)
        {
            Q_UNUSED(parent);
        }
    
        /* 重写run方法,继承QThread的类,只有run方法是在新的线程里 */
        void run() override
        {
            QString result = "线程开启成功";
            /* 发送结果准备好的信号 */
            emit resultReady(result);
    
            while (1) {
                emit resultReady("正在运行中......");
                sleep(1);
            }
        }
    
    signals:
        /* 声明一个信号,译结果准确好的信号 */
        void resultReady(const QString &s);
    };
    
    #endif // MAINWINDOW_H
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    mainwindow.cpp

    在 MainWindow 的析构函数里退出线程,然后判断线程是否退出成功。因为我们这个线程是没有循环操作的,直接点击按钮开启线程后,开始做耗时工作。按钮点击后开启线程,首先我们得判断这个线程是否在运行,如果不在运行我们则开始线程,开始线程用 start()方法,它会调用重写的 run()函数的。

    #include "mainwindow.h"
    
    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
    {
        /* 设置位置与大小 */
        this->setGeometry(0, 0, 800, 480);
    
        /* 对象实例化 */
        pushButton = new QPushButton(this);
        workerThread = new WorkerThread(this);
    
        /* 按钮设置大小与文本 */
        pushButton->resize(100, 40);
        pushButton->setText("开启线程");
    
        /* 信号槽连接 */
        connect(workerThread, SIGNAL(resultReady(QString)),
                this, SLOT(handleResults(QString)));
        connect(pushButton, SIGNAL(clicked()),
                this, SLOT(pushButtonClicked()));
    }
    
    MainWindow::~MainWindow()
    {
        /* 进程退出,注意本例run()方法没写循环,此方法需要有循环才生效 */
        workerThread->quit();
    
        /* 阻塞等待2000ms检查一次进程是否已经退出 */
        if (workerThread->wait(50)) {
            qDebug() << "线程已经结束!" << endl;
        }
    }
    
    void MainWindow::handleResults(const QString &result)
    {
        /* 打印出线程发送过来的结果 */
        qDebug() << result << endl;
    }
    
    void MainWindow::pushButtonClicked()
    {
        /* 检查线程是否在运行,如果没有则开始运行 */
        if (!workerThread->isRunning()) {
            workerThread->start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    main.cpp

    默认就是工程创建好的样子没有改动

    #include "mainwindow.h"
    
    #include <QApplication>
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        MainWindow w;
        w.show();
        return a.exec();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    第二种办法继承于 QObject 的类转移到一个 Thread 里

    mainwindow.h

    说明:我们把耗时的工作都放于槽函数下。工人可以有不同的工作,但是每次只能去做一份。这里不同于继承 QThread 类的线程 run(),继承 QThread 的类只有 run()在新线程里。而继承 QObject 的类,使用 moveToThread()可以把整个继承的 QObject 类移至线程里执行,所以可以有 doWork1(),doWork2…等等耗时的操作,但是这些耗时的操作都应该作为槽函数,由主线程去调用。进入循环后使用互拆锁判断 isCanRun 变量的状态,为假即跳出 while 循环,直到 doWork1 结束。注意,虽然 doWork1 结束了,但是线程并没有退出(结束)。因为我们把这个类移到线程里了,直到这个类被销毁。或者使用 quit()和 exit()退出线程才真正的结束!

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    #include <QThread>
    #include <QDebug>
    #include <QPushButton>
    #include <QMutexLocker>
    #include <QMutex>
    
    /* 工人类 */
    class Worker;
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        MainWindow(QWidget *parent = nullptr);
        ~MainWindow();
    
    private:
        /* 开始工作1线程按钮 */
        QPushButton *pushButton1;
    
        /* 打断线程按钮 */
        QPushButton *pushButton2;
    
        /* 开始工作2线程按钮 */
        QPushButton *pushButton3;
    
        /* 全局线程 */
        QThread workerThread;
    
        /* 工人类 */
        Worker *worker;
    
    private slots:
        /* 按钮1点击工作1线程 */
        void pushButton1Clicked();
    
        /* 按钮2点击打断线程 */
        void pushButton2Clicked();
    
        /* 按钮3点击工作2线程 */
        void pushButton3Clicked();
    
        /* 用于接收工人是否在工作的信号 */
        void handleResults(const QString &);
    
    signals:
        /* 工人开始工作(做些耗时的操作 ) */
        void startWork1(const QString &);
        void startWork2(const QString &);
    };
    
    /* Worker类,这个类声明了doWork1函数,将整个Worker类移至线程workerThread */
    class Worker : public QObject
    {
        Q_OBJECT
    
    private:
        /* 互斥锁 */
        QMutex lock;
    
        /* 标志位 */
        bool isCanRun;
    
    public slots:
        /* 耗时的工作都放在槽函数下,工人可以有多份不同的工作,但是每次只能去做一份 */
        void doWork1(const QString &parameter)
        {
    
            /* 标志位为真 */
            isCanRun = true;
            /* 死循环 */
            while (1) {
                /* 此{}作用是QMutexLocker与lock的作用范围,获取锁后,
                 * 运行完成后即解锁 */
                {
                    QMutexLocker locker(&lock);
                    /* 如果标志位不为真 */
                    if (!isCanRun) {
                        /* 跳出循环 */
                        break;
                    }
                }
                /* 使用QThread里的延时函数,当作一个普通延时 */
                QThread::sleep(1);
    
                emit resultReady(parameter + "doWork1函数" + "运行中......");
            }
            /* doWork1运行完成,发送信号 */
            emit resultReady("打断doWork1函数");
        }
    
        void doWork2(const QString &parameter)
        {
            /* 标志位为真 */
            isCanRun = true;
            /* 死循环 */
            while (1) {
                /* 此{}作用是QMutexLocker与lock的作用范围,获取锁后,
                 * 运行完成后即解锁 */
                {
                    QMutexLocker locker(&lock);
                    /* 如果标志位不为真 */
                    if (!isCanRun) {
                        /* 跳出循环 */
                        break;
                    }
                }
                /* 使用QThread里的延时函数,当作一个普通延时 */
                QThread::sleep(1);
    
                emit resultReady(parameter + "doWork2函数" + "运行中......");
            }
            /* doWork1运行完成,发送信号 */
            emit resultReady("打断doWork2函数");
        }
    
    public:
        /* 打断线程(注意此方法不能放在槽函数下) */
        void stopWork()
        {
            qDebug() << "打断线程" << endl;
    
            /* 获取锁后,运行完成后即解锁 */
            QMutexLocker locker(&lock);
            isCanRun = false;
        }
    
    signals:
        /* 工人工作函数状态的信号 */
        void resultReady(const QString &result);
    };
    #endif // MAINWINDOW_H
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138

    mainwindow.cpp

    快速了解继承 QObject 类线程的使用。
    在 MainWindow 类里使用。通过点击一个按钮开启线程。另一个按钮点击关闭线程。另外通过加锁的操作来安全的终止一个
    线程。(我们可以通过 QMutexLocker 可以安全的使用 QMutex 以免忘记解锁。)在我们谈谈为什么需要加锁来终止一个线程?因为 quit()和 exit()方法都不会中途终止线程。要马上终止一个线程可以用 terminate()方法。但是这个函数存在非常不安全的因素,Qt 官方文档说不推荐使用。我们可以添加一个 bool 变量,通过主线程修改这个 bool 变量来终止,但是有可能引起访问冲突,所以需要加锁,例程里可能体现不是那么明确,当我们有 doWork1(),doWork2…就能体现到 bool 变量加锁的作用了。但是加锁会消耗一定的性能,增加耗时。

    #include "mainwindow.h"
    
    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
    {
        /* 设置显示位置与大小 */
        this->setGeometry(0, 0, 800, 480);
        pushButton1 =  new QPushButton(this);
        pushButton2 =  new QPushButton(this);
        pushButton3 =  new QPushButton(this);
    
        /* 设置按钮的位置大小 */
        pushButton1->setGeometry(100, 200, 120, 40);
        pushButton2->setGeometry(300, 200, 120, 40);
        pushButton3->setGeometry(500, 200, 120, 40);
    
        /* 设置两个按钮的文本 */
        pushButton1->setText("开启工作1线程");
        pushButton2->setText("打断线程");
        pushButton3->setText("开启工作2线程");
    
        /* 工人类实例化 */
        worker = new Worker;
    
        /* 将worker类移至线程workerThread */
        worker->moveToThread(&workerThread);
    
        /* 信号槽连接 */
    
        /* 线程完成销毁对象 */
        connect(&workerThread, SIGNAL(finished()),
                worker, SLOT(deleteLater()));
        connect(&workerThread, SIGNAL(finished()),
                &workerThread, SLOT(deleteLater()));
    
        /* 发送开始工作的信号,开始工作 */
        connect(this, SIGNAL(startWork1(QString)),
                worker, SLOT(doWork1(QString)));
    
        /* 发送开始工作的信号,开始工作 */
        connect(this, SIGNAL(startWork2(QString)),
                worker, SLOT(doWork2(QString)));
    
        /* 接收到worker发送过来的信号 */
        connect(worker, SIGNAL(resultReady(QString)),
                this, SLOT(handleResults(QString)));
    
        /* 点击按钮开始线程 */
        connect(pushButton1, SIGNAL(clicked()),
                this, SLOT(pushButton1Clicked()));
    
        /* 点击按钮打断线程 */
        connect(pushButton2, SIGNAL(clicked()),
                this, SLOT(pushButton2Clicked()));
    
        connect(pushButton3, SIGNAL(clicked()),
                this, SLOT(pushButton3Clicked()));
    }
    
    MainWindow::~MainWindow()
    {
        /* 打断线程再退出 */
        worker->stopWork();
        workerThread.quit();
    
        /* 阻塞线程2000ms,判断线程是否结束 */
        if (workerThread.wait(2000)) {
            qDebug() << "线程结束" << endl;
        }
    }
    
    void MainWindow::pushButton1Clicked()
    {
        /* 字符串常量 */
        const QString str = "正在运行";
    
        /* 判断线程是否在运行 */
        if (!workerThread.isRunning()) {
            /* 开启线程 */
            workerThread.start();
        }
    
        /* 发送正在运行的信号,线程收到信号后执行后返回线程耗时函数 + 此字符串 */
        emit this->startWork1(str);
    }
    
    void MainWindow::pushButton3Clicked()
    {
        /* 字符串常量 */
        const QString str = "正在运行";
    
        /* 判断线程是否在运行 */
        if (!workerThread.isRunning()) {
            /* 开启线程 */
            workerThread.start();
        }
    
        /* 发送正在运行的信号,线程收到信号后执行后返回线程耗时函数 + 此字符串 */
        emit this->startWork2(str);
    }
    
    void MainWindow::pushButton2Clicked()
    {
        /* 如果线程在运行 */
        if (workerThread.isRunning()) {
    
            /* 停止耗时工作,跳出耗时工作的循环 */
            worker->stopWork();
        }
    }
    
    void MainWindow::handleResults(const QString &results)
    {
        /* 打印线程的状态 */
        qDebug() << "线程的状态:" << results << endl;
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118

    main.cpp

    默认就是工程创建好的样子没有改动

    #include "mainwindow.h"
    
    #include <QApplication>
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        MainWindow w;
        w.show();
        return a.exec();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    下面是演示效果

    C++线程使用

  • 相关阅读:
    Windows下防火墙端口配置
    图像分割:DeepLabV2网络简介
    HarmonyOS原子化服务案例分享-邻居
    显卡核心 短路 原因是什么?
    如何开启企业数字化转型
    Activiti工作流入门
    OpenRemote: Java 开源 IoT 物联网开发平台,匹配智慧城市、智能家居、能源管理
    SpringMVC-全面详解(学习总结---从入门到深化)
    《Unix 网络编程》08:基本UDP套接字编程
    深度学习中的性能参数名称
  • 原文地址:https://blog.csdn.net/weixin_50183638/article/details/125397562