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

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

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

相关文章

【项目新功能开发篇】需求分析和开发设计

作者介绍&#xff1a;本人笔名姑苏老陈&#xff0c;从事JAVA开发工作十多年了&#xff0c;带过大学刚毕业的实习生&#xff0c;也带过技术团队。最近有个朋友的表弟&#xff0c;马上要大学毕业了&#xff0c;想从事JAVA开发工作&#xff0c;但不知道从何处入手。于是&#xff0…

vue 加 websocket 聊天

<template><div style="height: 100%; width: 100%; background-color: #fff"><div class="wrap"><!-- 头部 --><div class="titleBox"><imgsrc="@/assets/image/avatar.png"style="argin: 10p…

分类预测 | Matlab实现TCN-BiGRU-Mutilhead-Attention时间卷积双向门控循环单元多头注意力机制多特征分类预测/故障识别

分类预测 | Matlab实现TCN-BiGRU-Mutilhead-Attention时间卷积双向门控循环单元多头注意力机制多特征分类预测/故障识别 目录 分类预测 | Matlab实现TCN-BiGRU-Mutilhead-Attention时间卷积双向门控循环单元多头注意力机制多特征分类预测/故障识别分类效果基本介绍模型描述程序…

Android adb 常用命令

以下是一些常用的adb&#xff08;Android Debug Bridge&#xff09;命令列表&#xff1a; 连接设备&#xff1a; adb devices&#xff1a;列出已连接的 Android 设备。adb connect <设备IP地址>&#xff1a;通过TCP/IP连接到设备。adb disconnect <设备IP地址>&…

SpringBoot多级多模块聚合项目下maven打包报‘packaging‘ with value ‘jar‘ is invalid.

问题场景&#xff1a; SpringBoot多级多模块项目中部分pom文件报红&#xff0c;mvn clean时报错&#xff1a;‘packaging’ with value ‘jar’ is invalid. Aggregator projects require ‘pom’ as packaging. 项目背景 项目是一个多级多模块项目&#xff0c;简单分为&…

【系统架构设计师】- 知识点汇总(易错总结)

—————————————————————————————————— 一、函数依赖的推理规则&#xff1a; 1、自反律&#xff1a;若Y⊆X⊆U&#xff0c;则X→Y在R上成立。 2、增广律&#xff1a;若X→Y在R上成立&#xff0c;且Z⊆U&#xff0c;则XZ→YZ在R上也成立。 3、传…

Vue-Next-Admin:适配手机、平板、PC的开源后台管理模板

摘要&#xff1a;随着移动设备和PC的普及&#xff0c;为了满足不同设备的需求&#xff0c;开发一个能够自适应手机、平板和PC的后台管理系统变得至关重要。本文将介绍一个基于Vue3.x、Typescript、Vite、Element Plus等技术的开源模板库——Vue-Next-Admin&#xff0c;帮助开发…

FebHost:人工智能时代的新宠儿.AI域名

近年来,人工智能技术在各行各业迅猛发展,正在深刻改变着我们的生活。作为AI领域的专属域名,.AI域名正成为越来越多企业和个人的首选。 那么,.AI域名到底是什么呢?它是一种特殊的顶级域名(Top-Level Domain, TLD),于2013年由 安哥拉政府正式退出。与其他通用顶级域名如.com、.…

华为ensp路由器模拟ftp服务器访问

众所周知ensp的pc只有ping功能&#xff0c;ssh、telnet、ftp都无法实现&#xff0c;所以想实现需要更换为路由器 R1需要FTP到server的ftp服务 server的FTP配置就这些命令&#xff0c;主要的是路径&#xff0c;然后在网络可达的情况下就可以进行登录测试了 aaa local-user hu…

uniapp通过蓝牙传输数据 (ios)

在uni-app中&#xff0c;可以通过uni-ble&#xff08;uni-app官方提供的蓝牙插件&#xff09;来实现iOS设备上的蓝牙数据传输。 首先&#xff0c;确保已在uni-app的manifest.json文件中添加uni-ble插件的配置&#xff1a; "permission": { "scope.userLocati…

Windows power shell/cmd/shell/脚本

Windows power shell/cmd/shell/脚本 1、shell shell&#xff1a;shell脚本运行的环境&#xff0c;是一个概念不是特指 shell脚本&#xff1a;shell脚本就是将一系列命令按照一定的顺序保存到一个文本文件中&#xff0c;该文本文件被赋予执行权限&#xff0c;执行时依次执行…

【大模型】大模型 CPU 推理之 llama.cpp

【大模型】大模型 CPU 推理之 llama.cpp llama.cpp安装llama.cppMemory/Disk RequirementsQuantization测试推理下载模型测试 参考 llama.cpp 描述 The main goal of llama.cpp is to enable LLM inference with minimal setup and state-of-the-art performance on a wide var…

unity 使用Base64编码工具对xml json 或者其他文本进行加密 解密

Base64编码加密解密工具 这是一个加密解密的网页工具&#xff0c;别人可以把他加密后的字符串给你&#xff0c;然后你可以用代码解密出来&#xff0c; 或者自己对内容进行加密&#xff0c;解密处理。 /// <summary>/// Base64 解码/// </summary>string DecodeBase…

基于 NGINX 的 ngx_http_geoip2 模块 来禁止国外 IP 访问网站

基于 NGINX 的 ngx_http_geoip2 模块 来禁止国外 IP 访问网站 一、安装 geoip2 扩展依赖 [rootfxkj ~]# yum install libmaxminddb-devel -y二、下载 ngx_http_geoip2_module 模块 [rootfxkj tmp]# git clone https://github.com/leev/ngx_http_geoip2_module.git三、解压模…

55、美国德克萨斯大学奥斯汀分校、钱德拉家族电气与计算机工程系:通过迁移学习解决BCI个体差异性[不得不说,看技术还得是老美]

2024年2月5日跨被试最新文章&#xff1a; 德州州立大学奥斯汀分校研究团队最近的一项研究成果&#xff0c;通过非侵入式的脑机接口&#xff0c;可以让被试不需要任何校准就可以使用脑机接口设备&#xff0c;这意味着脑机接口具备了大规模被使用的潜力。 一般来说&#xff0c;…

02 删改查

文章目录 CartControllerCartMapperCartDButilCartMapper.xmlgeneratorConfig.xmlmybatis-config.xmlcart.jspproducts.jsp(忽略)pom.xml CartController package controller;import mapper.CartMapper; import org.apache.ibatis.session.SqlSession; import pojo.Cart; impor…

UE4 方块排序动画

【动画效果】 入动画&#xff1a; 出动画&#xff1a; 【分析】 入动画&#xff1a;方块动画排序方式为Z字形&#xff0c;堆砌方向为X和Y轴向 出动画&#xff1a;方块动画排序方式为随机 【关键蓝图】 1.构建方块砌体 2.入/出动画

web渗透测试漏洞复现:ZooKeeper未授权漏洞复现

web渗透测试漏洞复现 1. ZooKeeper未授权漏洞复现1.1 ZooKeeper简介1.2 ZooKeeper漏洞复现1.3 ZooKeeper漏洞修复建议1. ZooKeeper未授权漏洞复现 1.1 ZooKeeper简介 ZooKeeper 是一个分布式的、开源的协调服务,最初由雅虎开发,现隶属于 Apache 软件基金会,是Google的Chub…

Java pdfbox 给 PDF 添加文字和图片水印 并旋转45度

POM <dependency><groupId>org.apache.pdfbox</groupId><artifactId>pdfbox</artifactId><version>2.0.27</version> </dependency> 代码&#xff1a; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdf…

人工智能+的广泛应用,已渗透到生活的方方面面

引言 随着科技的不断进步和人工智能技术的快速发展&#xff0c;我们正处于一个人工智能时代。人工智能不仅仅是一种技术&#xff0c;更是一种革命性的变革力量&#xff0c;它正在以前所未有的方式改变着我们的生活和工作方式。 人工智能&#xff08;AI&#xff09;指的是人工…