多线程-Qt-思维导图-学习笔记

多线程

在这里插入图片描述

继承 QThread 的线程

概述

  • 继承 QThread 是创建线程的一个普通方法

  • 线程内的方法执行

    • 在创建的线程中,只有 run() 方法是在该线程内执行的

    • 其他在类内定义的方法,尽管属于子线程类,但它们实际是在主线程内执行的

  • 主线程的方法执行

    • 线程内有很多方法,它们都是在主线程内执行的
  • 子线程的方法执行

    • 在子线程中,只有 run() 方法是在子线程内执行的
  • 重写 run() 方法

    • run() 方法是继承自 QThread 类的方法

    • 用户需要重写这个方法

    • 一般情况下,耗时的操作会被写在这个 run() 方法里面

应用实例

  • 基本流程

    • 通过 QThread 类继承线程

    • 然后在 MainWindow 类里使用


    • 过点击一个按钮开启线程。当线程执行完成时,会发送 resultReady(const QString &s)信号给主线程

  • mainwindow.h

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

    • 第 36 行,声明一个 WorkerThread 的类继承 QThread 类,这里是参考 Qt 的 QThread 类的帮助文档的写法

    • 第 47 行,重写 run()方法,这里很重要。把耗时操作写于此,相当于一个继承 QThread类线程模板了

  • mainwindow.cpp

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

    • 线程对象的实例化(第11行)

      • 在线程对象实例化时,Qt 使用 C++ 基本都是对象编程

      • Qt 线程也不例外,我们用对象来管理线程

    • 析构函数中的线程退出(第24~33行)

      • 在 MainWindow 的析构函数里退出线程

      • 判断线程是否退出成功

      • 由于该线程没有循环操作,因此在按钮点击后,执行了 2 秒延时操作后线程就完成了

      • 所以在析构函数里直接退出线程是可以的

    • 按钮点击后开启线程(第41~46行)

      • 按钮点击后开启线程

      • 首先判断该线程是否在运行

      • 如果线程不在运行,则开始线程

      • 开始线程使用 start() 方法,该方法会调用重写的 run() 函数

程序运行效果

  • 点击开启线程按钮后,延时 2s 后,Qt Creator 的应用程序输出窗口打印出“线程开启成功”。
    在 2s 内多次点击按钮则不会重复开启线程,因为线程在这 2s 内还在运行。同时我们可以看到
    点击按钮没卡顿现象

继承 QObject 的线程

概述

  • 继承 QObject 类是创建线程的另一种方法

    • 继承 QObject 类更加灵活
  • 使用 moveToThread() 方法

    • 通过 QObject::moveToThread() 方法,可以将一个 QObject 的类转移到一个线程里执行
  • 创建继承 QObject 的类

    • 首先,编写一个类继承 QObject
  • 将类移到 QThread 线程执行

    • 使用 QObject::moveToThread() 方法将该类移到一个 QThread 线程里执行
  • 主线程与 QThread 线程的通信

    • 可以通过主线程发送信号,来调用 QThread 线程的方法
  • QThread 线程内方法的执行

    • 例如,使用信号调用的 fun4(), fun5() 等方法,这些方法都是在 QThread 线程里执行的

应用实例

  • 基本思路

    • 通过 QObject 类继承线程,在 MainWindow 类里使用

      • 通过继承 QObject 类创建线程,并在 MainWindow 类里使用
    • 开启和关闭线程的操作

      • 通过点击一个按钮来开启线程

      • 通过另一个按钮点击关闭线程

    • 使用加锁操作来安全终止线程

      • 通过加锁操作来确保线程的安全终止

      • 可以使用 QMutexLocker 安全地使用 QMutex,避免忘记解锁的情况

    • 为什么需要加锁来终止线程

      • quit() 和 exit() 方法不会立即终止线程

      • terminate() 方法可以立即终止线程,但存在非常不安全的因素,Qt 官方文档不推荐使用。

    • 使用 bool 变量来终止线程

      • 可以添加一个 bool 变量,通过主线程修改该变量来终止线程

      • 这种方法可能会引起访问冲突,因此需要加锁

    • 加锁在多任务情况下的重要性

      • 在有多个任务(如 doWork1(), doWork2())时,加锁可以防止访问冲突

      • 加锁会消耗一定的性能,增加执行时间

  • mainwindow.h

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

    • 声明 Worker 类继承 QObject 类(第51~105行)

      • 声明一个名为 Worker 的类,继承自 QObject 类

      • 这里参考了 Qt 的 QThread 类的帮助文档,将官方例子运用到我们的例子中

    • 耗时工作放于槽函数中(第62~88行)

      • 将耗时的工作都放在槽函数下

      • 虽然工人可以有不同的工作,但每次只能做一份

      • 这与继承 QThread 类的线程不同,继承 QThread 类的线程只有 run() 在新线程里执行

      • 继承 QObject 的类使用 moveToThread() 可以将整个继承的 QObject 类移至线程里执行

      • 因此,可以有多个耗时操作方法如 doWork1(), doWork2() 等,但这些操作都应该作为槽函数,由主线程调用

    • 循环中的互斥锁判断(第67~80行)

      • 在循环中使用互拆锁判断 isCanRun 变量的状态

      • 如果 isCanRun 为假,则跳出 while 循环,直到 doWork1 结束

      • 注意,虽然 doWork1 结束了,但线程并没有退出(结束)

      • 因为我们将整个类移到了线程中,所以线程不会结束,除非类被销毁,或者使用 quit() 和 exit() 退出线程

  • mainwindow.cpp

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

    • 工人类实例化(第20行)

      • 在第20行,实例化工人类

      • 注意,继承 QObject 的多线程类不能指定父对象

    • 移至 workerThread 线程执行(第24行)

      • 工人类实例化后,将工人类对象移至 workerThread 线程里执行
    • 线程结束后的内存管理(第29~32行)

      • 在线程结束后,需要使用 deleteLater 来销毁 worker 对象和 workerThread 对象分配的内存

      • deleteLater 会在消息循环中确认没有这两个对象的事件后,再进行销毁

程序运行效果

  • 点击开启线程按钮后,应用程序输出窗口每隔 2 秒打印“正在运行 doWork1 函数”,当我们点击打断线程按钮后,窗口打印出“打断 doWork1 函数”。 点击打断线程,会打断 doWork1函数的循环,doWork1 函数就运行结束了。再点击开启线程,可以再次运行 doWork1 函数

  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木木不迷茫(˵¯͒¯͒˵)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值