详解Qt中使用线程

news/2024/6/15 17:15:02/文章来源:https://blog.csdn.net/oThink1/article/details/137290401

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_1034669.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Topaz Video AI for Mac v5.0.0激活版 视频画质增强软件

Topaz Video AI for Mac是一款功能强大的视频处理软件&#xff0c;专为Mac用户设计&#xff0c;旨在通过人工智能技术为视频编辑和增强提供卓越的功能。这款软件利用先进的算法和深度学习技术&#xff0c;能够自动识别和分析视频中的各个元素&#xff0c;并进行智能修复和增强&…

区块链web3智能合约开发学习-开发工具Remix(1)

学习区块链中常用语言solidity时&#xff0c;我们会用到特别的开发工具&#xff0c;对于学习前期&#xff0c;建议是将代码写到Remix IDE中进行编译部署和测试&#xff0c;这就是我们编写和交互智能合约的地方&#xff0c; 在线remix编译器&#xff1a; https://remix.ethereu…

腾讯云(CVM)托管进行权限维持

前言 刚好看到一个师傅分享了一个阿里云ECS实战攻防&#xff0c;然后想到了同样利用腾讯云CVM的托管亦可实现在实战攻防中的权限维持。 简介 腾讯云自动化助手&#xff08;TencentCloud Automation Tools&#xff0c;TAT&#xff09;是一个原生运维部署工具&#xff0c;它可…

单片机中的RAM vs ROM

其实&#xff0c;单片机就是个小计算机。大计算机少不了的数据存储系统&#xff0c;单片机一样有&#xff0c;而且往往和CPU集成在一起&#xff0c;显得更加小巧灵活。 直到90年代初&#xff0c;国内容易得到的单片机是8031&#xff1a;不带存储器的芯片&#xff0c;要想工作&a…

基于SpringBoot的“校园志愿者管理系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“校园志愿者管理系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统总体结构图 系统首页界面图 志愿者注册…

基于DCT和扩频的音频水印嵌入提取算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ......................................................................... N 10; %嵌入一…

每日一题:用c语言写(输入n个数(n小于等于100),输出数字2的出现次数)

目录 一、要求 二、代码 三、结果 ​四、注意 一、要求 二、代码 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() {//输入n个数&#xff08;n小于等于100&#xff09;&#xff0c;输出数字2的出现次数;int n[100] ;int num 0;int count 0;/…

华清远见STM32MP157开发板助力嵌入式大赛ST赛道MPU应用方向项目开发

第七届&#xff08;2024&#xff09;全国大学生嵌入式芯片与系统设计竞赛&#xff08;以下简称“大赛”&#xff09;已经拉开帷幕&#xff0c;大赛的报名热潮正席卷而来。嵌入式大赛截止今年已连续举办了七届&#xff0c;为教育部认可的全国普通高校大学生国家级A类赛事&#x…

关于Windows中的系统还原工具的知识,看这篇文章就差不多了

序言 Windows中的系统还原工具是可用的更有用的实用程序之一,通常是尝试修复Windows中的主要问题的第一步。 简而言之,Windows系统还原工具允许你执行的操作是还原到以前的软件、注册表和驱动程序配置(称为还原点)。这就像“撤消”对Windows的最后一次重大更改,将计算机…

golang语言系列:Web框架+路由 之 Gin

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是golang语言学习系列&#xff0c;本篇对Gin框架的基本使用方法进行学习 1.Gin框架是什么 Gin 是一个 Go (Golang) 编写的轻量级 http web 框架&#xff0c;运行速度非常快&#xff0c;如果你是性能和高效的追求者…

2024年京东云主机租用价格_京东云服务器优惠价格表

2024年京东云服务器优惠价格表&#xff0c;轻量云主机优惠价格5.8元1个月、轻量云主机2C2G3M价格50元一年、196元三年&#xff0c;2C4G5M轻量云主机165元一年&#xff0c;4核8G5M云主机880元一年&#xff0c;游戏联机服务器4C16G配置26元1个月、4C32G价格65元1个月、8核32G费用…

ALPHA开发板上PHY网络芯片LAN8720:常用的几个寄存器功能

一. 简介 正点原子的开发板 ALPHA开发板&#xff0c;有线网络硬件方案所使用的也是最常用的一种方案&#xff0c;IMX6ULL芯片内部是自带 MAC网络芯片的&#xff0c;所以&#xff0c;也就是采用 "SOC内部集成网络MAC外设 PHY网络芯片方案"。 前面一篇文章简单了解了…

基于OSPF的企业内网安全优化

1.拓扑 2.IP地址规划 设备/地址/vlan设备/地址汇聚交换机/VLAN10192.200.10.0/24汇聚交换机/VLAN20192.200.20.0/24汇聚交换机/VLAN30192.200.30.0/24汇聚交换机/VLAN40192.200.40.0/24汇聚交换机/VLAN50192.200.50.0/24汇聚交换机/VLAN60192.200.60.0/24防火墙/VLAN70/服务器…

uniapp-设置UrlSchemes从外部浏览器H5打开app

需求&#xff1a;外部浏览器H5页面&#xff0c;跳转到uniapp开发的原生app内部。 1、uniapp内部的配置&#xff1a; &#xff08;1&#xff09;打开manifest->App常用其他设置&#xff0c;如下&#xff0c;按照提示输入您要设置的urlSchemes&#xff1a; &#xff08;2&am…

JVS智能BI数据分析:图表的数据联动配置详解

图表的数据联动 图表的数据联动是指在可视化图表中&#xff0c;当一个图表的数据发生变化时&#xff0c;另一个图表中的数据也会自动更新。这种功能通常用于展示相互关联的数据集&#xff0c;帮助用户更直观地了解数据之间的关系和趋势。 我们先看看实际的效果&#xff0c;如下…

springboot配置文件application.properties,application.yml/application.yaml

application.properties Springboot提供的一种属性配置方式&#xff1a;application.properties 初始时配置文件中只有一行语句。 启动时&#xff0c;比如tomcat的端口号默认8080&#xff0c;路径默认。如果我们要改变端口号及路径之类的可以在application.properties中配置。…

Web日志/招聘网站/电商大数据项目样例【实时/离线】

Web服务器日志分析项目 业务分析 业务背景 ​ 某大型电商公司&#xff0c;产生原始数据日志某小时达4千五万条&#xff0c;一天日志量月4亿两千万条。 主机规划 &#xff08;可略&#xff09;日志格式&#xff1a; 2017-06-1900:26:36101.200.190.54 GET /sys/ashx/ConfigH…

视频汇聚/安防监控/视频存储EasyCVR平台EasyPlayer播放器更新:新增【性能面板】

视频汇聚/安防监控/视频存储平台EasyCVR基于云边端架构&#xff0c;可以在复杂的网络环境中快速、灵活部署&#xff0c;平台视频能力丰富&#xff0c;可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云…

黑马点评项目笔记 I

短信登录 将逻辑过期时间写入redis里面 Slf4j RestController RequestMapping("/user") public class UserController {Resourceprivate IUserService userService;Resourceprivate IUserInfoService userInfoService;/*** 发送手机验证码*/PostMapping("code&…

华为OD面试手撕算法-合并排序数组

题目描述 本题是leetcode一道简单题&#xff1a;合并两个有序数组&#xff0c;但是对于时间和空间复杂度面试官明确给出了限制。 // 给定两个排序后的数组 A 和 B&#xff0c;其中 A 的末端有足够的缓冲空间容纳 B。 编写一个方法&#xff0c;将 B 合并入 A 并排序。 // 初始化…