Qt 线程 QThread类详解

  金牌会员 | 2024-7-14 07:01:45 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 638|帖子 638|积分 1914

Qt 线程中QThread的使用

在进行桌面应用程序开辟的时候, 假设应用程序在某些情况下需要处理比力复杂的逻辑, 如果只有一个线程行止理,就会导致窗口卡顿,无法处理用户的相干操作。这种情况下就需要使用多线程,其中一个线程处理窗口事故,其他线程进行逻辑运算,多个线程各司其职,不光可以提高用户体验还可以提拔程序的实行服从
在 qt 中使用了多线程,有些事项是需要额外注意的:


  • 默认的线程在Qt中称之为窗口线程,也叫主线程,负责窗口事故处理或者窗口控件数据的更新
  • 子线程负责后台的业务逻辑处理,子线程中不能对窗口对象做任何操作,这些变乱需要交给窗口线程处理
  • 主线程和子线程之间如果要进行数据的通报,需要使用Qt中的信号槽机制
1. 线程类 QThread

Qt 中提供了一个线程类,通过这个类就可以创建子线程了,Qt 中一共提供了两种创建子线程的方式,后边会依次介绍其使用方式。先来看一下这个类中提供的一些常用 API 函数:
  1. // Qt中的线程可以设置优先级
  2. // 得到当前线程的优先级
  3. Priority QThread::priority() const;
  4. void QThread::setPriority(Priority priority);
  5. 优先级:
  6.     QThread::IdlePriority         --> 最低的优先级
  7.     QThread::LowestPriority
  8.     QThread::LowPriority
  9.     QThread::NormalPriority
  10.     QThread::HighPriority
  11.     QThread::HighestPriority
  12.     QThread::TimeCriticalPriority --> 最高的优先级
  13.     QThread::InheritPriority      --> 子线程和其父线程的优先级相同, 默认是这个
  14. start(): 启动线程,使线程进入运行状态,调用线程的run()方法。
  15. run(): 线程的执行函数,需要在该函数中编写线程所需执行的任务。
  16. quit(): 终止线程的事件循环,在下一个事件处理周期结束时退出线程。
  17. wait(): 阻塞当前线程,直到线程执行完成或超时。
  18. finished(): 在线程执行完成时发出信号。
  19. terminate(): 强制终止线程的执行,不推荐使用,可能导致资源泄漏和未定义行为。
  20. isRunning(): 判断线程是否正在运行。
  21. currentThreadId(): 返回当前线程的ID。
  22. yieldCurrentThread(): 释放当前线程的时间片,允许其他线程执行。
  23. msleep(): 让当前线程休眠指定的毫秒数。
复制代码
2 信号槽

  1. // 和调用 exit() 效果是一样的
  2. // 调用这个函数之后, 再调用 wait() 函数
  3. [slot] void QThread::quit();
  4. // 启动子线程
  5. [slot] void QThread::start(Priority priority = InheritPriority);
  6. // 线程退出, 可能是会马上终止线程, 一般情况下不使用这个函数
  7. [slot] void QThread::terminate();
  8. // 线程中执行的任务完成了, 发出该信号
  9. // 任务函数中的处理逻辑执行完毕了
  10. [signal] void QThread::finished();
  11. // 开始工作之前发出这个信号, 一般不使用
  12. [signal] void QThread::started();
复制代码
3 静态函数
  1. // 返回一个指向管理当前执行线程的QThread的指针
  2. [static] QThread *QThread::currentThread();
  3. // 返回可以在系统上运行的理想线程数 == 和当前电脑的 CPU 核心数相同
  4. [static] int QThread::idealThreadCount();
  5. // 线程休眠函数
  6. [static] void QThread::msleep(unsigned long msecs);        // 单位: 毫秒
  7. [static] void QThread::sleep(unsigned long secs);        // 单位: 秒
  8. [static] void QThread::usleep(unsigned long usecs);        // 单位: 微秒
复制代码
4 重写函数

  1. // 子线程要处理什么任务, 需要写到 run() 中
  2. [virtual protected] void QThread::run();
复制代码
2种线程使用方式

使用 QThread 时的一个常见误区。


  • QThread 实例存在于实例化它的旧线程中:

    • 当您创建一个 QThread 对象时,该对象会存在于创建它的线程中,而不是在新创建的线程中。
    • 这意味着,您调用 QThread 对象的方法和槽时,实际上是在创建该对象的线程中实行的,而不是在新创建的工作线程中。

  • 在新线程中调用槽:

    • 如果您盼望在新创建的工作线程中实行某些操作,比如调用槽函数,就不能直接将这些槽函数定义在 QThread 子类中。
    • 由于 QThread 子类的方法和槽都会在创建该对象的线程中实行,而不是在工作线程中实行。

  • 使用工作对象方法:

    • 为了在新创建的工作线程中实行操作,您需要定义一个独立的工作对象类,并将全部的工作逻辑封装在该类中。
    • 在工作线程中,您可以创建这个工作对象的实例,并在该实例上调用方法来实行工作。

使用 QThread 时需要注意区分线程的概念。QThread 对象本身存在于创建它的线程中,而不是在新创建的工作线程中。如果您盼望在工作线程中实行操作,需要使用独立的工作对象,而不是直接在 QThread 子类中实现。这样可以确保在正确的线程中实行您的工作逻辑。 
简化:在这里先记取
1.new QThread 是在当前代码位置创建出来的
2.线程调用槽函数是有坑点的。
3.最好使用工作对象方法

线程和工作逻辑



  • 主线程和子线程实行的顺序不确定,偶然主线程在前,偶然子线程在前。
  • 只有run()函数运行在子线程中,run调用子函数则在子线程调用子函数。
  1. class WorkerThread : public QThread
  2. {
  3.     Q_OBJECT
  4. public:
  5.     void run() override  //子线程工作函数
  6.     {
  7.         // 在新线程中执行耗时任务
  8.         for (int i = 0; i < 5; ++i)
  9.         {
  10.             qDebug() << "Worker thread doing work..." << i;
  11.             sleep(1);
  12.         }
  13.         emit finished();
  14.     }
  15. signals:
  16.     void finished();
  17. };
  18. int main(int argc, char *argv[])
  19. {
  20.     QCoreApplication a(argc, argv);
  21.     // 创建并启动工作线程
  22.     WorkerThread* workerThread = new WorkerThread;
  23.     QObject::connect(workerThread, &WorkerThread::finished, workerThread, &WorkerThread::deleteLater);
  24.     workerThread->start();
  25.      // 等待任务完成
  26.     workerThread->wait();
  27.     // 停止并退出事件循环
  28.     a.exit();
  29.     return a.exec();
  30. }
复制代码
这个例子演示了如何在一个全新的线程中实行耗时的工作任务。通过继承 QThread 并重写 run() 方法,我们可以在新线程中运行自定义的工作逻辑。这种方式与下列的例子有所不同,下列的例子是在工作对象中实行任务,而不是在 QThread 子类中。
线程和工作对象



  • 槽函数无论是线程的信号触发还是自定义信号触发,槽函数都在新线程里运行。
  • 成员函数和主函数运行在主线程当中。
  1. #include <QDebug>
  2. #include <QThread>
  3. class Worker : public QObject
  4. {
  5.     Q_OBJECT
  6. public:
  7.     Worker()
  8.     {
  9.     }
  10. public slots:
  11.     void doWork()
  12.     {
  13.         // 执行耗时的计算任务
  14.         for (int i = 0; i < 5; ++i)
  15.         {
  16.             qDebug() << "Worker thread doing work..." << i;
  17.             QThread::sleep(1);
  18.         }
  19.         // 计算完成后,发射 finished 信号
  20.         emit finished();
  21.     }
  22. signals:
  23.     void finished();
  24. };
  25. int main(int argc, char *argv[])
  26. {
  27.     QCoreApplication a(argc, argv);
  28.     // 创建工作线程
  29.     QThread* workerThread = new QThread;
  30.     // 创建工作对象实例,并将其移动到工作线程中
  31.     Worker* worker = new Worker;
  32.     worker->moveToThread(workerThread);//将其移动到工作线程中。这确保了 Worker 对象的所有操作都在工作线程中进行
  33.     // 连接信号和槽
  34.     QObject::connect(workerThread, &QThread::started, worker, &Worker::doWork);
  35.     QObject::connect(worker, &Worker::finished, workerThread, &QThread::quit);
  36.     //即使 main() 函数执行完毕,主线程退出,QThread 对象也不会立即被释放,而是会等待工作线程完成后再被删除。
  37.     QObject::connect(worker, &Worker::finished, worker, &Worker::deleteLater);
  38.     QObject::connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater);
  39.     // 启动工作线程
  40.     workerThread->start();
  41.     return a.exec();
  42. }
复制代码
在这个例子中,QThread 对象代表了实际的工作线程,而 Worker 对象包罗了实际的工作逻辑。这样,我们就可以在工作线程中实行耗时的计算任务,而不会阻塞主线程。当任务完成时,Worker 对象会发射 finished 信号,从而触发工作线程退出和对象整理。 
代码运行示例

重写线程类:work.h

  1. #ifndef WORK_H
  2. #define WORK_H
  3. #include<QDebug>
  4. #include<QThread>
  5. #include<QMutex>
  6. #include<QSemaphore>
  7. class work : public QThread
  8. {
  9.     Q_OBJECT
  10. public:
  11.     work();
  12.     virtual ~work(); // 添加虚拟析构函数
  13. public:
  14.     void run() override;
  15.     void stopAndWait() {  //完美退出
  16.         m_running = false;
  17.         wait();     //等待任务完成
  18.         exit();  //停止并退出事件循环
  19.         qDebug() << "stopAndWait";
  20.         deleteLater(); //安全地删除 QObject 及其子类对象 异步地删除对象,而不是立即删除 注意:该函数是线程安全,多次调用该函数是安全的;
  21.     }
  22. private:
  23.     bool m_running; //完美退出
  24.     static uint16_t index;
  25. };
  26. #endif // WORK_H
  27. #include "work.h"
  28. uint16_t work::index = 1;
  29. work::work()
  30. {
  31.     // 构造函数实现
  32.     m_running=true;
  33. }
  34. work::~work()
  35. {
  36.     // 虚拟析构函数实现
  37. }
  38. void work::run() {
  39.     // 执行耗时工作
  40.     while (m_running) {
  41.         qDebug() << "Worker thread doing run..." << this->index++;
  42.         sleep(1);
  43.     }
  44.     qDebug() << "Worker thread exiting.";
  45. }
复制代码
工作对象类

workthread.h
  1. #ifndef WORKTHREAD2_H
  2. #define WORKTHREAD2_H
  3. #include<QDebug>
  4. #include<QThread>
  5. class workthread2 : public QObject
  6. {
  7.     Q_OBJECT
  8. public:
  9.     workthread2()=default;
  10.     ~workthread2()=default;
  11. public:
  12.     enum class ThreadState { Idle, Running, Finished };
  13.     Q_ENUM(ThreadState)
  14. signals:
  15.     void finished();
  16. public slots:
  17.     void doWork()
  18.     {
  19.         // 执行耗时的计算任务
  20.         for (int i = 0; i < 5; ++i)
  21.         {
  22.             qDebug() << "Worker thread doing work..." << i;
  23.             QThread::sleep(1);
  24.         }
  25.         // 计算完成后,发射 finished 信号
  26.         emit finished();
  27.     }
  28. };
  29. #endif // WORKTHREAD2_H
复制代码
main函数

  1. #include <QCoreApplication>
  2. #include"work.h"
  3. #include<workthread2.h>
  4. #include<QTimer>
  5. void qtThread_text(){
  6.     //第一种 继承重写run方法
  7.     QVector<work*> workerThreads;
  8.     // 创建并启动工作线程
  9.     for(int i=0;i<3;i++)
  10.     {
  11.         workerThreads.append(new work());
  12.     }
  13.     for(int i=0;i<3;i++)
  14.     {
  15.         workerThreads.at(i)->start();
  16.     }
  17.     // 等待一段时间后,通知线程退出
  18.     QThread::sleep(1);
  19.     for(work* data :workerThreads){
  20.         data->stopAndWait();
  21.         //data->deleteLater();
  22.     }
  23.     //    for(int i=0;i<3;i++)
  24.     //    {
  25.     //        workerthread[i]->wait();
  26.     //        workerthread[i]->terminate(); //发送线程终止信号
  27.     //    }
  28.     //第2种 使用对象模式 注意此模式是在当前线程运行
  29.     QThread* newthread= new QThread;
  30.     QThread* newthread2= new QThread;
  31.     // 启动工作线程 一个对象只能隶属于一个线程
  32.     workthread2* work2 = new workthread2;
  33.     workthread2* work2_2 = new workthread2;
  34.     work2->moveToThread(newthread);
  35.     work2_2->moveToThread(newthread2);
  36.     // 启动工作线程
  37.    newthread->start();
  38.    newthread2->start();
  39.    // 连接信号和槽
  40.     QObject::connect(newthread, &QThread::started, work2, &workthread2::doWork);
  41.     QObject::connect(work2, &workthread2::finished, newthread, &QThread::quit);
  42.     QObject::connect(work2, &workthread2::finished, work2, &workthread2::deleteLater);
  43.     QObject::connect(newthread, &QThread::finished, newthread, &QThread::deleteLater);
  44.     QObject::connect(newthread2, &QThread::started, work2_2, &workthread2::doWork);
  45.     QObject::connect(work2_2, &workthread2::finished, newthread2, &QThread::quit);
  46.     QObject::connect(work2_2, &workthread2::finished, work2_2, &workthread2::deleteLater);
  47.     QObject::connect(newthread2, &QThread::finished, newthread2, &QThread::deleteLater);
  48.     newthread->start();
  49.     newthread2->start();
  50. /*
  51.     newthread->wait();
  52.     newthread->exit();
  53.     当 newthread->wait() 被调用时,它会阻塞当前线程(即主线程)直到 newthread 线程退出。然后 newthread->exit() 被调用,停止并退出了事件循环。
  54.     qDebug() << "a.exec()"; 这行代码就不会被执行。
  55. */
  56.     // 等待线程完成并退出事件循环
  57.    newthread->wait();
  58.    newthread2->wait();
  59.    newthread->exit();
  60.    newthread2->exit();
  61. }
  62. int main(int argc, char *argv[])
  63. {
  64.     QCoreApplication a(argc, argv);
  65.     qtThread_text();
  66.     // 手动调用 a.exec()
  67.     qDebug() << "a.exec()";
  68.     return a.exec(); //开启Qt 的事件循环
  69. }
复制代码
运行效果


总结

上述提到的两种使用 QThread 的方式有以下几点主要区别: (第一种重写 第二种movetoThread)

  • 线程的创建方式
  • ***线程与工作对象的关系:

    • 在第一种方式中,线程和工作逻辑是耦合在一起的,由于工作逻辑直接在 QThread 的子类中实现。
    • 在第二种方式中,线程和工作对象是解耦的,工作逻辑被封装在独立的 Worker 对象中。

  • ***信号槽的实行位置:

    • move版本槽函数无论是线程的信号触发还是自定义信号触发,槽函数都在新线程里运行。
    • 重写版本信号槽是在主线程实行。

  • 线程安全性:

    • 在第二种方式中,由于工作对象是在工作线程中运行的,因此不需要担心线程安全性题目。
    • 在第一种方式中,如果在 QThread 的子类中访问了共享资源,就需要特别注意线程安全性。

  • 机动性和可重用性:
    重写机动性和可重用性低,move版本低耦合
  • 错误处理:

    • 在第二种方式中,可以更容易地在工作对象中捕获和处理错误,由于工作逻辑被封装在了独立的对象中。
    • 在第一种方式中,错误处理可能更加困难,由于工作逻辑和线程紧耦合在一起。

总的来说,主要区别在于代码结构和筹划模式,而不是线程的创建方式。无论接纳哪种方式,都是在主线程中创建和启动了工作线程。由于4.8更新了movetothread版本,既然是更新,阐明这种方法更好,建议大家在使用时接纳movetothread版本

参考文献:

Qt 线程中QThread的使用_qt qthread-CSDN博客
QThread 类 | Qt 核心 5.15.17 --- QThread Class | Qt Core 5.15.17
一文搞定之Qt多线程(QThread、moveToThread)_qthread movetothread-CSDN博客
最后附上源代码链接
对您有帮助的话,帮忙点个star


36-qthread-qmutex-qsemaphore · jbjnb/Qt demo - 码云 - 开源中国 (gitee.com)

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表