【Qt】Qt中多线程的使用

ops/2024/9/22 19:08:29/

Qt的多线程机制允许开发者在单个应用程序进程中创建并行任务,从而实现高效的资源利用和流畅的用户体验。

1. QThread 类

  • Qt 提供了 QThread 类来实现跨平台的多线程功能。
  • 每个 QThread 对象代表一个操作系统级别的线程。
  • 不建议直接在 QThread 对象的子类中覆盖 run() 函数并在其中执行长耗时任务;而是应该创建一个工作类,继承自 QObject,并在线程中移动这个工作对象,使其在新线程的事件循环中执行任务。

头文件

#include<QThread>

所属模块:

QT += core

2. 线程的创建与启动

  • 创建一个新的线程通常包括实例化一个 QThread 对象,创建一个要在新线程上执行任务的工作类实例,并将该工作类移动到新线程中。
  • 通过调用 QThread::start() 方法启动线程,这会触发 QThread 对象的 run() 函数,但在现代Qt实践下,run() 函数主要用于启动事件循环而非执行具体业务逻辑。

3. 线程间通信

  • Qt的信号和槽机制可用于线程间的通信,由于它是类型安全且异步的,所以特别适合于多线程环境。
  • 使用 moveToThread() 函数将一个QObject移动到另一个线程中后,其发出的信号会在目标线程的事件循环中排队,通过连接信号到槽,可以在不同线程之间安全地传递数据和控制流。
// Worker.h
#include <QObject>class Worker : public QObject
{Q_OBJECT
public:using QObject::QObject;public slots:void doWork() {// 这里放置你要在新线程中执行的具体任务for (int i = 0; i < 1000000; ++i) {// 假设这是一个耗时的操作// ...qDebug() << "Working in thread:" << QThread::currentThreadId();}emit workFinished();}signals:void workFinished();
};// 主程序
#include <QCoreApplication>
#include <QThread>
#include "Worker.h"int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);// 创建QThread对象QThread workerThread;// 创建Worker对象Worker worker;// 将Worker对象移动到新线程中worker.moveToThread(&workerThread);// 连接信号槽,当线程开始运行时,执行doWork()connect(&workerThread, &QThread::started, &worker, &Worker::doWork);// 当Worker完成任务时,退出线程connect(&worker, &Worker::workFinished, &workerThread, &QThread::quit);// 当线程结束时,删除Worker对象connect(&workerThread, &QThread::finished, &worker, &QObject::deleteLater);// 启动线程workerThread.start();// 等待线程结束workerThread.wait();return a.exec();
}

在这个例子中,我们创建了一个Worker类,其中doWork槽函数包含我们要在新线程中执行的实际任务。我们将Worker对象移动到新创建的QThread中,并通过信号槽机制连接QThread::started信号和Worker::doWork槽函数。
当调用workerThread.start()时,新线程的事件循环开始运行,进而触发Worker::doWork方法执行。
注意,这里并没有直接在QThread的子类中重写run()函数来执行任务。

4. 线程同步

  • Qt提供了多种线程同步原语,如 QMutexQWaitConditionQSemaphoreQReadWriteLock 等,用于解决多线程环境下的数据竞争和同步问题。
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <QDebug>class WorkerThread : public QThread
{Q_OBJECT
public:WorkerThread(QMutex* mutex, QWaitCondition* condition): m_mutex(mutex), m_condition(condition) {}protected:void run() override{while (!shouldStop()) {// 请求资源m_mutex->lock();// 检查是否可以继续工作(此处仅为示例,实际场景可能更为复杂)if (canProceed()) {processData();emit dataProcessed();} else {// 如果不能继续,则等待条件满足m_condition->wait(m_mutex);}// 释放资源m_mutex->unlock();}}private:bool canProceed() const { /* 实现检查条件的方法 */ }void processData() { /* 执行需要同步的耗时操作 */ }bool shouldStop() const { /* 检查是否需要停止线程 */ }signals:void dataProcessed();private:QMutex* m_mutex;QWaitCondition* m_condition;
};int main(int argc, char *argv[])
{QApplication app(argc, argv);QMutex mutex;QWaitCondition condition;WorkerThread worker(&mutex, &condition);connect(&worker, &WorkerThread::dataProcessed, [](){qDebug() << "Data processed!";});worker.start();// 假设主线程在某一时刻改变了条件// ...mutex.lock();// 更新状态或数据condition.wakeOne();  // 唤醒等待的worker线程mutex.unlock();// 在适当时候停止worker线程...return app.exec();
}

在这个例子中,WorkerThread类在一个循环中尝试获取互斥锁(QMutex),然后检查是否可以执行任务。如果条件不允许(例如,数据还没有准备好),则调用m_condition->wait(m_mutex),这会使线程进入等待状态,直到收到信(wakeOne()wakeAll())告知条件已改变。

主线程可以通过锁定互斥锁,更新相关状态或数据,然后唤醒等待的线程,这样就实现了线程间的同步。当worker线程被唤醒后,会重新检查条件并执行相应任务。通过这种方式,可以有效地避免数据竞争问题,并确保线程在合适的时候执行操作。

5. 注意事项

  • 避免直接在UI线程之外修改GUI组件,因为Qt的GUI必须在主线程中更新。若需在工作线程中操作UI,应使用信号槽机制或者其他线程安全的方式来通知主线程进行界面更新。
  • 主线程负责窗口事件处理(鼠标拖动/窗口缩放)或窗口控件数据更新,子线程不能对窗口对象做任何操作。
    (子线程没有权限对窗口做任何读写操作,必须把数据发送给主线程,由主线程操作)

6. 最佳实践

  • 自Qt 4.8以来,推荐的做法是不直接继承 QThread 类,而是将要并发执行的任务封装到一个单独的QObject派生类中,让QThread负责运行这些对象的事件循环。

http://www.ppmy.cn/ops/7589.html

相关文章

【深度学习-番外1】Win10系统搭建VSCode+Anaconda+Pytorch+CUDA深度学习环境和框架全过程

专栏的老读者们都知道&#xff0c;以前的文章以使用MATLAB的为多。 不过后续陆续开始展开深度学习算法的应用&#xff0c;就会逐渐引入Python语言了&#xff08;当然MATLAB的代码也会同步更新&#xff09;&#xff0c;这是由于在深度学习领域&#xff0c;Python应用更为广泛。…

【大语言模型LLM】-使用大语言模型搭建点餐机器人

关于作者 行业&#xff1a;人工智能训练师/LLM 学者/LLM微调乙方PM发展&#xff1a;大模型微调/增强检索RAG分享国内大模型前沿动态&#xff0c;共同成长&#xff0c;欢迎关注交流… 大语言模型LLM基础-系列文章 【大语言模型LLM】-大语言模型如何编写Prompt?【大语言模型LL…

说说redis的集群的原理吧

为什么redis集群这么牛&#xff1f;为什么其拥有那么多的优点了&#xff1f;为什么大厂用的都是redis集群&#xff1f; 因为其 1.高可用 2.高性能 3.数据容错性 4.数据持久化 5.线性扩展 6.客户端重定向 为什么有以上这么多优势了&#xff1f; 其实跟redis集群背后的原理有关&a…

K8s: 集群内Pod通信机制之环境变量

集群内Pod通信机制之环境变量 Kubernetes 支持两种基本的服务发现模式 —— 环境变量和 DNS 1 &#xff09; 环境变量概述 在Service里面通过label selector选择器去匹配到对应的pod然后把流量导给对应的pod进行这个service的一个服务提供也就是说你只要访问service的IP地址…

设计模式-构建者模式

作者持续关注 WPS二次开发专题系列&#xff0c;持续为大家带来更多有价值的WPS二次开发技术细节&#xff0c;如果能够帮助到您&#xff0c;请帮忙来个一键三连&#xff0c;更多问题请联系我&#xff08;QQ:250325397&#xff09; 目录 定义 特点 使用场景 优缺点 (1) 优点 …

09 SQL进阶 -- SQL高级处理 -- 窗口函数等

1. 窗口函数 1.1 窗口函数概念及基本的使用方法 窗口函数也称为 OLAP 函数。OLAP 是 OnLine AnalyticalProcessing 的简称,意思是对数据库数据进行实时分析处理。 为了便于理解,称之为窗口函数。常规的 SELECT 语句都是对整张表进行查询,而窗口函数可以让我们有选择的去某…

抽象工厂模式

抽象工厂模式 定义&#xff1a; 为一个产品家族提供了统一的创建接口。当需要这个产品家族的某一系列的时候&#xff0c;可以从抽象工厂中选出相对系的系列来创建一个具体的工厂类别 示例&#xff1a; 动物有吃饭和说话的行为&#xff0c;小猫吃猫粮并说喵星语&#xff0c;小…

校园水电能源智能化管理系统

校园作为大量人员集聚的场所&#xff0c;水电能源的高效管理对于降低运营成本、保障安全稳定供应以及推动可持续发展至关重要。校园水电能源智能化管理系统应运而生&#xff0c;通过先进技术的应用&#xff0c;实现了对校园水电资源的智能监控、计量和管理。本文将从系统背景、…