Qt - 多线程之线程同步

news/2024/12/25 13:20:00/文章来源:https://www.cnblogs.com/zhuchunlin/p/18557344

一、线程为什么要同步

使用两个线程对一个全局变量做累加,从0加到10,所以只要每个线程累加到5就行。代码如下所示:

#include <QApplication>
#include <QThread>
#include <QDebug>// 定义共享资源
int sharedValue = 0;// 定义一个线程类
class MyThread : public QThread
{
public:void run() override{for(int i = 0; i < 5; i++){sharedValue++; // 访问共享资源qDebug() << "Thread ID: " << QThread::currentThreadId() << " - Shared Value: " << sharedValue;msleep(1000); // 线程休眠1秒}}
};int main(int argc, char *argv[])
{QApplication a(argc, argv);MyThread thread1;MyThread thread2;thread1.start();thread2.start();thread1.wait();thread2.wait();qDebug() << "Final Shared Value: " << sharedValue;return a.exec();
}

运行结果:

可以看到两个线程不是交替着执行的,会重复执行,所以并不是我们想要的结果。一般这种问题需要采用线程同步的方式。

结果分析: 明显看出在未加锁情况下对临界资源的访问出现混乱的结果。

 

二、线程同步方式

1、QMutex(互斥锁)

互斥锁用于保护共享资源,确保在同一时间只有一个线程能够访问该资源。线程在访问共享资源之前需要获取互斥锁,使用完后再释放互斥锁,以确保同一时间只有一个线程在执行关键代码段。

 

QMutex的使用步骤如下:

1.创建QMutex对象:在需要进行线程同步的地方,首先创建一个QMutex对象。

 
QMutex mutex;

2.获取互斥锁:在访问共享资源之前,线程需要获取互斥锁。使用lock()方法获取互斥锁。如果互斥锁已被其他线程占用,当前线程会被阻塞,直到互斥锁被释放。

 
mutex.lock();

3.访问共享资源:获取到互斥锁后,线程可以安全地访问共享资源。

 
// 访问共享资源的代码

4.释放互斥锁:在线程完成对共享资源的访问之后,需要释放互斥锁,以便其他线程可以获取到互斥锁进行访问

 
mutex.unlock();

示例代码:

 
#include <QApplication>
#include <QThread>
#include <QDebug>
#include <QMutex>// 定义共享资源
int sharedValue = 0;
// 定义互斥锁
QMutex mutex;// 定义一个线程类
class MyThread : public QThread
{
public:void run() override{for(int i = 0; i < 5; i++){mutex.lock(); // 加锁sharedValue++; // 访问共享资源qDebug() << "Thread ID: " << QThread::currentThreadId() << " - Shared Value: " << sharedValue;msleep(1000); // 线程休眠1秒mutex.unlock(); // 解锁}}
};int main(int argc, char *argv[])
{QApplication a(argc, argv);MyThread thread1;MyThread thread2;thread1.start();thread2.start();thread1.wait();thread2.wait();qDebug() << "Final Shared Value: " << sharedValue;return a.exec();
}

运行效果:

 

2、QReadWriteLock(读写锁)

 读写锁是Qt中用于实现读写线程同步的一种机制。它提供了一种更高效的方式来管理对共享资源的读写操作,允许多个线程同时进行读操作,但只允许一个线程进行写操作。

 

QReadWriteLock的使用步骤如下:

1.创建QReadWriteLock对象:在需要进行读写线程同步的地方,首先创建一个QReadWriteLock对象。

 
QReadWriteLock rwLock;

2.获取读锁:当线程需要进行读操作时,使用lockForRead()方法获取读锁。多个线程可以同时获取读锁,以进行并发的读操作。

 
rwLock.lockForRead();

3.进行读操作:获取到读锁后,线程可以安全地进行读操作,访问共享资源

 
// 进行读操作的代码

4.释放读锁:在读操作完成后,使用unlock()方法释放读锁,允许其他线程获取读锁。

 
rwLock.unlock();

5.获取写锁:当线程需要进行写操作时,使用lockForWrite()方法获取写锁。写锁是独占的,只允许一个线程获取写锁进行写操作,其他线程需要等待写锁的释放。

 
rwLock.lockForWrite();

6.进行写操作:获取到写锁后,线程可以安全地进行写操作,修改共享资源。

 
// 进行写操作的代码

7.释放写锁:在写操作完成后,使用unlock()方法释放写锁,允许其他线程获取读锁或写锁。

 
rwLock.unlock();

示例代码:

 
#include <QApplication>
#include <QThread>
#include <QDebug>
#include <QReadWriteLock>QString sharedData; // 共享数据变量QReadWriteLock rwLock; // 读写锁// 读取操作线程
class ReaderThread : public QThread
{
public:void run() override{rwLock.lockForRead(); // 以读取方式加锁qDebug() << "Read Data: " << sharedData; // 输出读取的数据rwLock.unlock(); // 释放锁}
};// 写入操作线程
class WriterThread : public QThread
{
public:void run() override{rwLock.lockForWrite(); // 以写入方式加锁sharedData = "Hello, world!"; // 写入数据qDebug() << "Write Data: " << sharedData; // 输出写入的数据rwLock.unlock(); // 释放锁}
};int main(int argc, char *argv[])
{QApplication a(argc, argv);// 创建读取线程和写入线程WriterThread writer;ReaderThread reader;// 启动线程writer.start(); // 启动写入线程reader.start(); // 启动读取线程// 等待线程结束writer.wait(); // 等待写入线程结束reader.wait(); // 等待读取线程结束return a.exec();
}

执行结果:

 

3、QWaitCondition(等待条件)

 QWaitCondition是Qt中用于线程同步的一种机制,它允许线程等待特定条件的发生,并在条件满足时被唤醒继续执行。QWaitCondition通常与QMutex一起使用,以提供更复杂的线程同步机制。

QWaitCondition的使用步骤如下:

1.创建QWaitCondition对象:在需要进行线程同步的地方,首先创建一个QWaitCondition对象。

 
QWaitCondition condition;

2.创建QMutex对象:为了保护条件的读写操作,创建一个QMutex对象。

 
QMutex mutex;

3.在等待条件的线程中等待:在线程需要等待特定条件的发生时,使用wait()方法使线程进入等待状态。

 
mutex.lock();
condition.wait(&mutex);
mutex.unlock();

在调用wait()方法之前,需要先获取到互斥锁(QMutex)。这样可以确保线程在等待之前能够安全地访问和检查条件。

4.在其他线程中满足条件并唤醒等待线程:当某个条件满足时,通过wakeOne()wakeAll()方法唤醒等待的线程。

 
mutex.lock();
condition.wakeOne(); // 或者使用 condition.wakeAll();
mutex.unlock();

示例代码:

 
#include <QApplication>
#include <QThread>
#include <QDebug>
#include <QWaitCondition>
#include <QMutex>
#include <QQueue>QMutex mutex;  // 创建一个互斥锁,确保线程安全
QWaitCondition queueNotEmpty; // 创建一个条件变量,表示队列非空
QQueue<int> queue; // 创建一个队列用于存储数据// 生产者线程
class ProducerThread : public QThread
{
public:void run() override{for (int i = 0; i < 10; ++i) {// 生产数据并加入队列{QMutexLocker locker(&mutex); // 加锁queue.enqueue(i); // 生产数据并加入队列qDebug() << "Produced: " << i;queueNotEmpty.wakeOne();  // 通知消费者队列非空}msleep(100);  // 休眠一段时间}}
};// 消费者线程
class ConsumerThread : public QThread
{
public:void run() override{for (int i = 0; i < 10; ++i) {// 检查队列是否为空,如果为空则等待{QMutexLocker locker(&mutex); // 加锁while (queue.isEmpty()) {queueNotEmpty.wait(&mutex); // 等待条件变量,直到队列非空}int value = queue.dequeue();  // 从队列中取出数据qDebug() << "Consumed: " << value;}msleep(200);  // 休眠一段时间}}
};int main(int argc, char *argv[])
{QApplication a(argc, argv);// 创建生产者线程和消费者线程ProducerThread producer;ConsumerThread consumer;// 启动线程producer.start();consumer.start();// 等待线程结束producer.wait();consumer.wait();return a.exec();
}

执行结果:

 

4、QSemaphore(信号量)

QSemaphore 是 Qt 中用于实现信号量的类,用于控制对共享资源的访问数量。它可以用来限制同时访问某一资源的线程数量,也可以用于线程之间的同步。QSemaphore 可以被获取和释放,当信号量的值为正时,线程可以获得该信号量;当信号量的值为零时,线程将被阻塞,直到有线程释放信号量。通过获取和释放信号量,可以实现线程之间的协调和资源的管理。

示例代码:

 
#include <QApplication>
#include <QThread>
#include <QDebug>
#include <QSemaphore>QSemaphore semaphore(2); // 定义能够同时访问资源的线程数量为2的信号量class MyThread : public QThread // 定义一个线程类
{
public:void run() override{if(semaphore.tryAcquire()){ // 尝试获取信号量qDebug() << "Thread ID: " << QThread::currentThreadId() << " - Acquired Semaphore"; // 输出线程ID和已获取信号量消息sleep(2); // 线程休眠2秒qDebug() << "Thread ID: " << QThread::currentThreadId() << " - Releasing Semaphore"; // 输出线程ID和释放信号量消息semaphore.release(); // 释放信号量}else{qDebug() << "Thread ID: " << QThread::currentThreadId() << " - Semaphore not acquired"; // 输出线程ID和未获取信号量消息}}
};int main(int argc, char *argv[]) // 主函数
{QApplication a(argc, argv); // 创建应用程序对象MyThread thread1; // 创建线程对象1MyThread thread2; // 创建线程对象2MyThread thread3; // 创建线程对象3thread1.start(); // 启动线程1thread2.start(); // 启动线程2thread3.start(); // 启动线程3thread1.wait(); // 等待线程1结束thread2.wait(); // 等待线程2结束thread3.wait(); // 等待线程3结束return a.exec(); // 执行应用程序事件循环
}

执行结果:

当QSemaphore semaphore(2):

当QSemaphore semaphore(3):

 

================================================

参考文档:https://blog.csdn.net/qq_45790916/article/details/136390241

参考文档:https://www.cnblogs.com/TechNomad/p/17439960.html

================================================

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

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

相关文章

el-input 输入框加%号后缀

<el-input><i slot="suffix">%</i> </el-input> 如图学如逆水行舟,不进则退

人工智能之机器学习线代基础——克拉默法则(Cramers Rule)

克拉默法则是一种用于解 线性方程组 的方法,适用于系数矩阵为 方阵 的情况(即未知数的个数与方程的个数相等)。它通过计算行列式直接求解方程组的解。 克拉默法则的优缺点 优点直接性:可以显式地通过行列式计算出解。 理论价值:适合小规模问题,易于理解和验证解的正确性…

CommonsBeanUtils1(基于ysoserial)

环境准备 JDK1.8(8u421) JDK8的版本应该都没什么影响,这里直接以我的镜像为准了、commons-beanutils:commons-beanutils:1.9.2、commons-collections:commons-collections:3.2、javassist:javassist:3.12.0.GA mvn中加入以下依赖: <dependency><groupId>commons-…

【Mastercam 2025下载与安装教程 含补丁】

mastercam 2025是一款专门用于数控加工的软件,广泛应用于航空、汽车、机械制造等领域可以帮助工程师、机械加工师等专业人员高效地进行零件的建模和加工,提高生产效益和质量。 系统要求‌操作系统‌:Windows 7或更高版本,64位操作系统。 ‌处理器‌:CPU频率至少为2GHz。 ‌内…

《刚刚问世》系列初窥篇-Java+Playwright自动化测试-5-创建首个自动化脚本(详细教程)

1.简介 前面几篇宏哥介绍了两种(java和maven)环境搭建和浏览器的启动方法,这篇文章宏哥将要介绍第一个自动化测试脚本。前边环境都搭建成功了,浏览器也驱动成功了,那么我们不着急学习其他内容,首先宏哥搭建好的环境中创建首个完整的自动化测试脚本,让小伙伴或者童鞋们提…

2024年腾讯云双十一薅羊毛最强攻略:错过一次又得等一年!

2024年腾讯云双十一活动为用户提供了丰富的云产品和优惠福利,以下是一份省钱、省心、省力的购买攻略,帮助大家制定必抢清单: 一、活动时间与入口活动时间:即日起至2024年11月30日23:59:59,具体以页面变更为准。 活动入口:腾讯云双十一活动页面二、优惠活动概览 1.服务器限…

BGP路由控制

BGP路由控制概述 BGP协议的重点不在于发现和计算路由,而在于通过丰富的属性和策略实现对路由的控制。 控制BGP路由可以通过两种方式实现。 一是通过BGP 的基本属性实现对BGP 选路的控制。这种方式比较简单,主要是通过配置、修改BGP 基本属性值以影响协议的选路,从而实现控制…

Windows右键新建列表排序

前言全局说明Windows右键新建列表排序一、说明 环境: Windows 11 家庭版 23H2 22631.3737二、Windows11 右键新建列表排序 2.1 打开注册表项 HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Discardable\PostSetup\ShellNew2.2 编辑 Classes 键 调整成…

如何让领导轻松在本地查看Allure报告

如何让领导轻松在本地查看Allure报告 问题描述 当我们把精心生成的Allure报告原始文件发送给领导后,领导直接打开index.html文件时,页面却一直处于加载状态,无法显示数据。通过F12开发者工具检查,我们发现这是由于浏览器跨域请求问题导致的。具体来说,当尝试通过XMLHttpRe…

使用yt-dlp下载youtube高清2k 60fps视频

只演示windows下的操作,linux和mac应该差不多的命令行。 首先放上github仓库地址:https://github.com/yt-dlp/yt-dlp 它的介绍: 厉害啊,支持数千个网站。 支持的网站列表在这里:https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md 我们往下一点,找到: 下…

私有云部署在互联网公司中的应用案例解析

在互联网公司中,私有云的部署与应用案例已经成为许多企业实现数字化转型、提高资源利用效率、加强数据安全等目标的重要手段。私有云为公司提供了高度的控制权和定制化选项,尤其适用于对数据安全性和合规性有较高要求的公司。下面,我们将通过几个典型的应用案例来分析私有云…

10、Oracle三种监听方式

客户端对监听的三种连接方式 1、专用服务器模式专用服务器模式默认是启用的 专用服务器模式特点: 1、一个连接对应一个server process 好处:这个连接发送的SQL会被马上处理 坏处:即使这个连接空闲,这个server process还是存在,也需要占用资源,至少是内存资源 2、这种连接…