当前位置: 首页 > news >正文

详解Qt中使用线程

详解Qt中使用线程

Qt中的线程相关知识涵盖了线程创建、管理、通信以及线程安全等方面。下面将详细讲解这些知识点,并提供对应的示例代码。

线程创建与管理

QThread类

Qt通过QThread类来创建和管理线程。要创建一个新的工作线程,通常有两种方法:

方法一:直接创建QThread子类

创建一个继承自QThread的类,并重写run()方法来指定线程执行的任务。

class MyThread : public QThread {Q_OBJECTpublic:explicit MyThread(QObject *parent = nullptr) : QThread(parent) {}protected:void run() override {// 在这里编写线程执行的任务for (int i = 0; i < 100; ++i) {qDebug() << "Thread working: " << i;msleep(100); // 模拟耗时任务}}
};// 使用示例
MyThread *thread = new MyThread(this);
thread->start(); // 启动线程
方法二:使用工作对象与QThread配合

创建一个实现了run()方法的工作类,并将其移入QThread实例中。这种方法更符合“单一职责原则”,将线程管理与线程任务分离。

class Worker : public QObject {Q_OBJECTpublic:explicit Worker(QObject *parent = nullptr) : QObject(parent) {}public slots:void run() {// 在这里编写线程执行的任务for (int i = 0; i < 100; ++i) {qDebug() << "Worker running: " << i;msleep(100); // 模拟耗时任务}}
};// 使用示例
QThread *thread = new QThread(this);
Worker *worker = new Worker();
worker->moveToThread(thread); // 将工作对象移入线程connect(thread, &QThread::started, worker, &Worker::run); // 当线程启动时,触发工作对象的run()方法
connect(worker, &QObject::destroyed, thread, &QThread::quit); // 工作对象销毁时,让线程退出
connect(thread, &QThread::finished, thread, &QObject::deleteLater); // 线程结束后删除线程对象thread->start(); // 启动线程

线程通信与同步

实际使用线程过程中,我们将很大的精力用在线程通信与同步中。接下来详细说一下。

信号与槽

Qt的信号槽机制支持跨线程通信。当一个线程中的对象发出信号时,连接到该信号的槽函数可以在另一个线程中执行。由于信号槽机制内部已经处理了线程同步问题,因此它是线程间安全的数据交换方式。

class Worker : public QObject {Q_OBJECTQ_PROPERTY(int progress READ getProgress NOTIFY progressChanged)public:explicit Worker(QObject *parent = nullptr) : QObject(parent), m_progress(0) {}int getProgress() const { return m_progress; }public slots:void processData() {for (int i = 0; i <= 100; ++i) {m_progress = i;emit progressChanged(i);msleep(100); // 模拟耗时任务}}signals:void progressChanged(int value);private:int m_progress;
};// 主线程中接收进度更新
connect(worker, &Worker::progressChanged, this, [this](int value) {ui->progressBar->setValue(value);
});// 启动工作线程
QThread *thread = new QThread(this);
Worker *worker = new Worker();
worker->moveToThread(thread);
connect(thread, &QThread::started, worker, &Worker::processData);
thread->start();

互斥锁(QMutex)、信号量(QSemaphore)与条件变量(QWaitCondition)

对于更复杂的线程同步需求,可以使用Qt提供的同步机制:

  • QMutex用于保护临界区,防止多个线程同时访问同一块数据。
  • QSemaphore用于控制同时访问共享资源的线程数量。
  • QWaitCondition允许线程在特定条件不满足时挂起自己,直到条件满足后再被唤醒。
QMutex使用示例

QMutex是用来实现线程同步的一种工具,它可以确保同一时间内只允许一个线程访问受保护的资源。下面是一个简单的QMutex使用例子,展示如何在两个线程中安全地访问和修改共享资源:

#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QMutex>class SharedResource : public QObject {Q_OBJECT
public:explicit SharedResource(QObject *parent = nullptr) : QObject(parent), count(0), mutex(new QMutex()) {}void increment() {mutex->lock();count++;qDebug() << "Count incremented from thread:" << QThread::currentThreadId();mutex->unlock();}int getCount() const {QMutexLocker locker(mutex.get());return count;}private:int count;QSharedPointer<QMutex> mutex;
};// 工作线程类
class WorkerThread : public QThread {Q_OBJECT
public:WorkerThread(SharedResource *resource, QObject *parent = nullptr) : QThread(parent), resource(resource) {}protected:void run() override {for (int i = 0; i < 100; ++i) {resource->increment();msleep(100); // 模拟耗时操作}}private:SharedResource *resource;
};int main(int argc, char *argv[])
{QCoreApplication app(argc, argv);// 创建共享资源SharedResource sharedResource;// 创建并启动两个工作线程WorkerThread thread1(&sharedResource);WorkerThread thread2(&sharedResource);thread1.start();thread2.start();// 等待两个线程都完成thread1.wait();thread2.wait();qDebug() << "两个线程结束后,count最终值为:" << sharedResource.getCount();return app.exec();
}#include "main.moc"

在这个例子中,定义了一个名为SharedResource的类,其中包含一个整型变量count,并且使用了一个QMutex来保护这个变量。在increment()方法中加锁解锁来保证线程安全地递增count值。

此外,创建了一个名为WorkerThread的线程类,它在运行时会调用SharedResource的increment()方法。启动两个WorkerThread实例,它们会在各自的线程中同时尝试增加count的值,但由于QMutex的存在,这两个线程会交替访问并修改count,确保了数据的安全性。

最后,主线程等待所有工作线程完成后,输出最终的count值,展示经过多线程并发操作后得到的结果

QSemaphore使用示例

QSemaphore在Qt中用于管理有限的资源,它主要用于解决生产者-消费者问题、控制访问许可的数量以及其他类型的并发控制场景。下面是一个简化的QSemaphore使用例子,模拟了一个生产者线程向缓冲区写入数据,消费者线程从缓冲区读取数据的过程:

#include <QCoreApplication>
#include <QThread>
#include <QSemaphore>
#include <QDebug>// 缓冲区大小
const int BufferSize = 10;// 循环缓冲区
char buffer[BufferSize];
QSemaphore freeSlots(BufferSize); // 初始化空闲槽数量为缓冲区大小
QSemaphore usedSlots(0); // 初始化已使用槽数量为0// 生产者线程类
class Producer : public QThread {
public:void run() override {while (true) {freeSlots.acquire(); // 请求一个空闲槽位// 这里省略了实际数据生产的代码buffer[index] = 'P'; // 假设生成一个字符数据usedSlots.release(); // 释放一个槽位,表示已填充数据qDebug() << "Producer produced data at index:" << index;index = (index + 1) % BufferSize;msleep(100); // 模拟生产延迟}}private:int index = 0;
};// 消费者线程类
class Consumer : public QThread {
public:void run() override {while (true) {usedSlots.acquire(); // 请求一个已使用槽位// 这里省略了实际数据消耗的代码qDebug() << "Consumer consumed data at index:" << index;freeSlots.release(); // 释放一个槽位,表示已消费数据index = (index + 1) % BufferSize;msleep(200); // 模拟消费延迟}}
};int main(int argc, char *argv[])
{QCoreApplication app(argc, argv);Producer producer;Consumer consumer;producer.start();consumer.start();producer.wait();consumer.wait();return app.exec();
}#include "main.moc"

在这个例子中:

  • 我们创建了两个信号量:freeSlots代表缓冲区中可用的空闲位置数量,初始化为缓冲区大小;usedSlots代表已被占用的位置数量,初始化为0。

  • 生产者线程每次运行时,首先获取一个空闲槽位(freeSlots.acquire()),然后假定填入数据,之后释放一个已使用槽位(usedSlots.release())。

  • 消费者线程则相反,首先获取一个已使用槽位(usedSlots.acquire()),接着假定消费掉数据,然后释放一个空闲槽位(freeSlots.release())。

通过这种方式,QSemaphore确保了任何时候缓冲区中被占用的槽位不超过其容量,同时也确保了生产者不会在没有空闲槽位的情况下继续生产,消费者也不会在没有数据可消费的情况下继续消费。

注意,这个例子为了简洁起见省略了一些细节,比如实际的数据生产和消费过程,以及线程安全的索引管理等。在实际项目中,还需根据具体情况进行适当的错误处理和边界条件检查。

QWaitCondition使用示例

QWaitCondition在Qt中用于线程间的同步,当某个条件不满足时,线程可以进入等待状态,直到另一个线程改变了条件并唤醒等待的线程。下面是一个简化的QWaitCondition使用示例,模拟了一个生产者线程向队列添加数据,消费者线程从队列移除数据,并且在队列为空时消费者线程会等待生产者线程添加数据的情况:

#include <QThread>
#include <QWaitCondition>
#include <QMutex>
#include <QQueue>QQueue<int> dataQueue;
QMutex queueMutex;
QWaitCondition dataAvailable;class Producer : public QThread {
public:void run() override {for (int i = 0; i < 100; ++i) {queueMutex.lock();if (dataQueue.isEmpty()) {qDebug() << "Producing data: " << i;dataQueue.enqueue(i);// 数据已添加,通知消费者线程数据可用dataAvailable.wakeOne();}queueMutex.unlock();// 模拟生产延迟msleep(100);}}
};class Consumer : public QThread {
public:void run() override {forever {queueMutex.lock();while (dataQueue.isEmpty()) {// 队列为空,消费者线程等待数据dataAvailable.wait(&queueMutex);}if (!dataQueue.isEmpty()) {int value = dataQueue.dequeue();qDebug() << "Consuming data: " << value;}queueMutex.unlock();// 模拟消费延迟msleep(50);}}
};int main(int argc, char *argv[])
{QCoreApplication app(argc, argv);Producer producer;Consumer consumer;producer.start();consumer.start();producer.wait();// 在实际情况中,可能需要更好的机制来终止消费者线程,例如通过信号或中断循环return app.exec();
}#include "main.moc"

在这个例子中:

  1. 定义了一个受保护的队列dataQueue和一个互斥量queueMutex来保证对队列操作的线程安全性。
  2. 使用QWaitCondition实例dataAvailable来同步生产者和消费者线程。
  3. 生产者线程在循环中向队列添加数据,并在数据添加后调用dataAvailable.wakeOne()来唤醒至少一个等待的消费者线程。
  4. 消费者线程在循环中尝试从队列中取出数据,如果发现队列为空,则调用dataAvailable.wait(&queueMutex)进入等待状态,直到收到生产者线程的通知。

通过这样的方式,生产者和消费者能够有效地同步工作,消费者不会在无数据可消费时浪费CPU资源,而是会等待生产者准备好数据后再继续执行。

线程安全与资源管理

线程安全

在多线程环境下,访问共享数据时必须确保线程安全。常见的策略有:

  • 互斥访问:使用QMutex、QReadWriteLock等工具保护临界区。
  • 无状态函数:设计线程任务函数不依赖任何外部状态,仅接受参数和返回结果。
  • 线程本地存储:使用QThreadStorage存储线程私有数据,避免数据竞争。

资源管理

  • 线程生命周期:确保线程在完成任务后能正常退出,并清理相关资源。如使用QThread::wait()等待线程结束,或在工作类的析构函数中调用QThread::quit()和QThread::wait()。
  • 异常处理:在线程任务函数中妥善处理异常,防止因异常导致线程无法正常退出。

使用建议

  • 避免过度线程化:过多的线程可能导致上下文切换频繁,反而降低性能。根据任务性质和系统资源合理设置线程数量。
  • 优先使用高级API:对于简单并行计算任务,考虑使用QtConcurrent库提供的函数(如QtConcurrent::run()、QtConcurrent::map()等),它们能自动管理线程池,简化编程。

综上,Qt提供了丰富的线程相关类和函数,帮助开发者实现多线程编程。通过正确使用这些工具,可以有效提升应用程序的响应速度、并发处理能力和资源利用率,同时需要注意线程安全、资源管理和线程间通信等问题。上述示例代码展示了创建线程、使用信号槽进行线程间通信以及线程同步的基本用法。

相关文章:

详解Qt中使用线程

详解Qt中使用线程 Qt中的线程相关知识涵盖了线程创建、管理、通信以及线程安全等方面。下面将详细讲解这些知识点&#xff0c;并提供对应的示例代码。 线程创建与管理 QThread类 Qt通过QThread类来创建和管理线程。要创建一个新的工作线程&#xff0c;通常有两种方法&#…...

在.Net6中用gdal实现第一个功能

目录 一、创建.NET6的控制台应用程序 二、加载Gdal插件 三、编写程序 一、创建.NET6的控制台应用程序 二、加载Gdal插件 Gdal的资源可以经过NuGet包引入。右键单击项目名称&#xff0c;然后选择 "Manage NuGet Packages"&#xff08;管理 NuGet 包&#xff09;。N…...

采用大语言模型进行查询重写——Query Rewriting via Large Language Models

文章&#xff1a;Query Rewriting via Large Language Models&#xff0c;https://arxiv.org/abs/2403.09060 摘要 查询重写是在将查询传递给查询优化器之前处理编写不良的查询的最有效技术之一。 手动重写不可扩展&#xff0c;因为它容易出错并且需要深厚的专业知识。 类似地…...

使用Vue实现CSS过渡和动画

01-初识动画和过渡 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>使用vue实现css过渡和动画&l…...

一家购物商场的数据运营挑战

✅作者简介&#xff1a;《数据运营&#xff1a;数据分析模型撬动新零售实战》作者、《数据实践之美》作者、数据科技公司创始人、多次参加国家级大数据行业标准研讨及制定、高端企培合作讲师。 &#x1f338;公众号&#xff1a;风姑娘的数字视角&#xff0c;免费分享数据应用相…...

React Native框架开发APP,安装免费的图标库(react-native-vector-icons)并使用详解

一、安装图标库 要使用免费的图标库&#xff0c;你可以使用 React Native Vector Icons 库。 首先&#xff0c;确保你已经安装了 react-native-vector-icons&#xff1a; npm install --save react-native-vector-iconsnpm install --save-dev types/react-native-vector-ic…...

idea端口占用

报错&#xff1a;Verify the connector‘s configuration, identify and stop any process that‘s listening on port XXXX 翻译&#xff1a; 原因&#xff1a; 解决&#xff1a; 一、重启大法 二、手动关闭 启动spring项目是控制台报错&#xff0c;详细信息如下&#xff…...

MQ消息队列详解以及MQ重复消费问题

MQ消息队列详解以及MQ重复消费问题 1、解耦2、异步调用3、流量削峰4、MQ重复消费问题&#xff0c;以及怎么解决&#xff1f;4.1、重复消费产生4.2、解决方法&#xff1a; https://blog.csdn.net/qq_44240587/article/details/104630567 核心的就是&#xff1a;解耦、异步、削锋…...

系统IO函数接口

目录 前言 一. man手册 1.1 man手册如何查询 1.2 man手册基础 二.系统IO函数接口 三.open打开文件夹 3.1 例1 open打开文件 3.2 open打开文件代码 3.3 例2 创建文件 四.write写文件 4.1 write写文件 五. read读文件 5.1 read读文件与偏移 5.2 偏移细节 5.3 read读文件代码 六.复…...

06 监听器

文章目录 SessionAttListenerDemo.javaSessionListenerDemo.javaProductController.java SessionAttListenerDemo.java package com.aistart.listener;import javax.servlet.ServletContext; import javax.servlet.annotation.WebListener; import javax.servlet.http.HttpSess…...

C语言第三十九弹---预处理(上)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 预处理 1、预定义符号 2、#define定义常量 3、#define定义宏 4、带有副作用的宏参数 5、宏替换的规则 6、宏和函数的对比 总结 在C语言中&#xff0c;预处…...

计算机视觉无人驾驶技术:入门指南

I. 引言&#xff1a; 计算机视觉无人驾驶技术是一种基于计算机视觉和机器学习技术的自动化驾驶技术。它可以通过搭载各种传感器和摄像机&#xff0c;让车辆自主感知周围环境&#xff0c;实现尽可能自动化的驾驶操作。 这种技术具有重要性和优势&#xff0c;包括&#xff1a; …...

Golang和Java对比

其实我是Javaer转的Golang&#xff0c;我谈谈自己对Java和Golang的体会 我先讲讲我认为Golang的优点 1、Golang是一门新语言&#xff0c;相比于Java&#xff0c;他的生态要小很多&#xff0c;优点很明显&#xff0c;自由度高&#xff0c;学习成本低&#xff0c;能快速拉起一个…...

2024.2.29力扣每日一题——统计可能的树根数目

2024.2.29 题目来源我的题解方法一 深度搜索&#xff08;暴力&#xff09; 超时方法二 树形动态规划 题目来源 力扣每日一题&#xff1b;题序&#xff1a;2581 我的题解 方法一 深度搜索&#xff08;暴力&#xff09; 超时 以每个节点node为跟进行深度搜索&#xff0c;并在搜…...

同一个主机配置多个SSH key

使用git时&#xff0c;我们可能一个git客户端使用多个git服务器&#xff0c;比如github&#xff0c;自建gitlab&#xff0c;gitee&#xff0c;为了防止提交混乱&#xff0c;所以需要一一对应生成公私钥。 第一步&#xff1a; 使用ssh-keygen生成多对密钥对&#xff0c;比如&…...

SAP系统财务模块简介:实现财务管理的卓越之道

作为全球领先的企业管理软件提供商&#xff0c;SAP公司开发了一系列强大而全面的财务模块&#xff0c;帮助企业实现财务管理的高效运作与优化。SAP系统的财务模块涵盖了财务核算、成本管理、资金管理、资产会计等多个方面&#xff0c;为企业提供了完整的财务管理解决方案。本文…...

【pytest】功能特性及常用插件

pytest是一个功能强大的Python测试框架&#xff0c;它的语法简洁明了&#xff0c;易于学习和使用。同时&#xff0c;它提供了丰富的功能和插件&#xff0c;使得测试过程更加灵活和高效。 功能特性 pytest的主要功能特性包括&#xff1a; 参数化测试&#xff1a;允许使用不同…...

基于SpringBoot和Vue的房产销售系统的设计与实现

今天要和大家聊的是一款基于SpringBoot和Vue的房产销售系统的设计与实现 &#xff01;&#xff01;&#xff01; 有需要的小伙伴可以通过文章末尾名片咨询我哦&#xff01;&#xff01;&#xff01; &#x1f495;&#x1f495;作者&#xff1a;李同学 &#x1f495;&#x1f…...

ROS2从入门到精通1-2:详解ROS2服务通信机制与自定义服务

目录 0 专栏介绍1 服务通信模型2 服务模型实现(C)3 服务模型实现(Python)4 自定义服务5 话题、服务通信的异同 0 专栏介绍 本专栏旨在通过对ROS2的系统学习&#xff0c;掌握ROS2底层基本分布式原理&#xff0c;并具有机器人建模和应用ROS2进行实际项目的开发和调试的工程能力。…...

vue两个特性和什么是MVVM

一、什么是vue 1.构建用户界面 用vue往html页面中填充数据&#xff0c;非常的方便 2.框架 框架是一套线成的解决方案 vue的指令、组件&#xff08;是对ui结构的复用&#xff09;、路由、vuex 二、vue的特性 1.数据驱动视图 2.双向数据绑定 1.数据驱动视图 数据的变化会驱动…...

CAD Plant3D 2023 下载地址及安装教程

CAD Plant3D是一款专业的三维工厂设计软件&#xff0c;用于在工业设备和管道设计领域进行建模和绘图。它是Autodesk公司旗下的AutoCAD系列产品之一&#xff0c;专门针对工艺、石油、化工、电力等行业的设计和工程项目。 CAD Plant3D提供了一套丰富的工具和功能&#xff0c;帮助…...

集成电路企业tapeout,如何保证机台数据准确、完整、高效地采集?

Tapeout即流片&#xff0c;集成电路行业中将CDS最终版电路图提交给半导体制造厂商进行物理生产的过程。在芯片设计与制造的流程中&#xff0c;Tapeout是非常重要的阶段&#xff0c;包括了布局&#xff08;Layout&#xff09;、连线&#xff08;Routing&#xff09;、分析&#…...

Nginx三大常用功能“反向代理,负载均衡,动静分离”

注意&#xff1a;以下案例在Windows系统计算机作为宿主机&#xff0c;Linux CentOS 作为虚拟机的环境中实现 一&#xff0c;Nginx配置实例-反向代理 1.反向代理 案例一 实现效果&#xff1a;使用nginx反向代理&#xff0c;访问 www.123.com 直接跳转到127.0.0.1:8080 准备工…...

类方法介绍、使用细节

...

Java SpringBoot中优雅地判断一个对象是否为空

在Java中&#xff0c;可以使用以下方法优雅地判断一个对象是否为空&#xff1a; 使用Objects.isNull()方法判断对象是否为空&#xff1a; import java.util.Objects;if (Objects.isNull(obj)) {// obj为空的处理逻辑 }使用Optional类优雅地处理可能为空的对象&#xff1a; impo…...

算法——矩阵:对于边界元素的处理

. - 力扣&#xff08;LeetCode&#xff09; 题目简述&#xff1a;扫雷&#xff0c;点击一个格子&#xff0c;返回整个地图的下一个状态。 对于边界元素&#xff0c;可以设置两个数组&#xff0c;index_row&#xff0c;index_col&#xff0c;遍历到一个格子需要搜索其周围格子…...

Git分支提交时自动大写 fatal: the remote end hung up unexpectedly

先说结论&#xff1a; 进入 .git/refs/heads目录&#xff0c;会看到Feature文件夹&#xff0c;重命名为feature即可。 表现&#xff1a; 通过终端命令创建的分支 git checkout -b feature/name 使用git push后自动变成了Feature/name 并且有时候在本地创建feature/1234567…...

隐私计算实训营第七讲-隐语SCQL的架构详细拆解

隐私计算实训营第七讲-隐语SCQL的架构详细拆解 文章目录 隐私计算实训营第七讲-隐语SCQL的架构详细拆解1.SCQL Overview1.1 多方数据分析场景1.2 多方数据分析技术路线1.2.1 TEE SQL方案1.2.2 MPC SQL方案 1.3 Secure Collaborative Query Language(SCQL)1.3.1 SCQL 系统组件1.…...

Android JNI开发定义全局变量

要在 C 文件中设置一个 string 类型的全局变量&#xff0c;让其他 C 文件都可以访问&#xff0c;并且可以通过 JNI 方法修改这个变量&#xff0c;可以按照以下步骤进行操作 定义全局变量&#xff1a; 在一个头文件&#xff08;比如 common.h&#xff09;中定义一个全局的 strin…...

docker容器部署gitlab的runner的shell模式注册下job中无法使用docker指令

引言 现需通过gitlab-runner来构建jar部署的镜像,发现在job中无法使用docker指令,解决的过程中出现一系列异常,在此做个问题解决的记录。 内容 通过docker-compose部署 name: java-env services:env-gitlab-runner:restart: alwaysimage: env/gitlab-runner-java:latest…...