Qt 线程同步机制 互斥锁 信号量 条件变量 读写锁

qt线程同步

Qt提供了丰富的线程同步机制来帮助开发者更高效和安全地进行多线程编程。其主要包括:

  • QMutex:为共享数据提供互斥访问能力,避免同时写入导致的数据冲突。利用lock()/unlock()方法实现锁定和解锁。

  • QReadWriteLock:读写锁,允许多个读线程同时访问,但写操作需要独占锁。适用于读操作频繁,写操作较少的场景。

  • QSemaphore:信号量,用来限制特定资源的访问量。例如一次最多N个线程可以访问缓冲区。通过acquire()/release()实现获取和释放许可。

  • QWaitCondition:条件变量,可以让线程等待一个条件满足后被唤醒。配合QMutex完成更为复杂的同步需求。

  • Qfutures/QtConcurrent:提供了基于线程池的并行计算能力。将耗时任务放入线程池,自动进行并行执行与结果汇总。

  • QEventLoop/Signals&Slots:事件循环与信号槽机制可以实现复杂的线程间通信,例如定时器、Progress Reporting等。

  • QThread:Qt原生的线程类,通过moveToThread()实现在线程间安全地传递QObject。

互斥锁

QMutex / QMutexLocker 

    QMutex 是 Qt 中用于实现互斥锁的类,用于保证在多线程程序中访问共享资源的互斥性。它提供了两个基本操作:lock() 和 unlock(),分别用于加锁和解锁。

        QMutexLocker 是 QMutex 的 RAII 风格封装,可以自动释放锁资源,避免忘记解锁而导致的死锁情况。QMutexLocker 在创建时会自动调用 QMutex 的 lock() 方法,析构时会自动调用 QMutex 的 unlock() 方法。因此使用 QMutexLocker 可以大大减少忘记解锁的情况。

QMutex mutex;void func() {{QMutexLocker locker(&mutex);//临界区域}mutex.lock();//临界区域mutex.unlock();
}
互斥锁实现买票同步程序

此处实现多线程重写版完美退出机制

#ifndef SELLER_H
#define SELLER_H#include<QThread>
#include<QMutex>
#include<QDebug>
class seller :public QThread
{Q_OBJECT
public:seller()=default;seller(int* data,QMutex *m){tickets=data;mtx=m;}
signals:void ticketsFinsh();
public:void run() override{while ((*tickets) > 0) { //多线程经典问题 双锁机制mtx->lock();if((*tickets) > 0){mtx->unlock();mtx->lock();qDebug()<<this->objectName()<<" : " <<--(*tickets);mtx->unlock();QThread::usleep(100);}}ticketsFinsh();}
private:int* tickets; // 票QMutex *mtx;
};#endif // SELLER_H#include <QCoreApplication>#include"seller.h"
int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);int tickets =100;QMutex mtx;seller* t1 =new seller(&tickets,&mtx);seller* t2 =new seller(&tickets,&mtx);seller* t3 =new seller(&tickets,&mtx);t1->setObjectName("Thread 1");t2->setObjectName("Thread 2");t3->setObjectName("Thread 3");//设置线程完成信号与主线程进行连接,主线程知道子线程任务结束。QObject::connect(t1, &seller::finished, t1, &seller::quit);QObject::connect(t2, &seller::finished, t2, &seller::quit);QObject::connect(t3, &seller::finished, t3, &seller::quit);//线程对象在离开线程后删除,但不代表线程任务真正完成。QObject::connect(t1, &seller::finished, t1, &seller::deleteLater);QObject::connect(t2, &seller::finished, t2, &seller::deleteLater);QObject::connect(t3, &seller::finished, t3, &seller::deleteLater);//线程对象在离开线程 结束应用的通知QObject::connect(t1, &seller::finished, &a, &QCoreApplication::quit);QObject::connect(t2, &seller::finished, &a, &QCoreApplication::quit);QObject::connect(t3, &seller::finished, &a, &QCoreApplication::quit);//start与run的区别:调用run()直接在当前线程执行任务代码。 start()用于启动一个线程,将任务移动到新线程运行。它会调用run()函数,但run()此时会在新线程的上下文中执行。t1->start();t2->start();t3->start();t1->wait();t2->wait();t3->wait();return a.exec();
}

QSemaphore 信号量

成员函数

acquire(int n = 1):从信号量中获取n个信号量(如果当前信号量可用)。如果数量不足,则阻塞等待其可用。成功返回后信号量减少n个。
release(int n = 1):向信号量中增加n个信号量。其他正在等待的线程可能因此被唤醒。
tryAcquire(int n = 1):尝试从信号量中获取n个信号量,,但不阻塞等待。如果成功返回true并减去n,否则返回false。
available():返回当前信号量的数量。
cancelAcquire(n):取消acquire操作的等待,释放阻塞的线程。
tryAcquire(int n,int timeout):尝试获取n个信号量,等待最长timeout毫秒,成功或超时均返回。
tryAcquire(QSemaphore::Time timeout):tryAcquire的重载版本,等待时间采用Time类型。这些方法提供了完整的获取/释放机制,开发者可以根据不同的同步需求灵活选择:
acquire()用于需要阻塞等待的情况;
tryAcquire()用于非阻塞获取,如有限资源池;
设置超时tryAcquire()可以防止死锁。

 基于信号量实现一个家庭买票

#ifndef SELLER_H
#define SELLER_H#include<QThread>
#include<QSemaphore>
#include<QtDebug>
#include<QMutex>
class seller : public QObject
{Q_OBJECTpublic:seller()=default;seller(QSemaphore* sem) : sem(sem) {i=0;}signals:void emptyTicket();
public slots:void display(){while(sem->tryAcquire(3)) {qDebug() << this->objectName() << "售出:"<<++i<<"个家庭"<<" 剩余票数:"<<sem->available();QThread::yieldCurrentThread(); //让出当前时间片}emit emptyTicket();}private:int i;QSemaphore *sem;};#endif // SELLER_H
#include <QCoreApplication>
#include"seller.h"
int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);QThread t1,t2,t3; //3个工作人员售票/** 空串原因t1.setObjectName("Thread1");t2.setObjectName("Thread 2");t3.setObjectName("Thread 3");
*/QSemaphore sem(99); // 33个家庭,每个家庭3个人 99张票 以家庭为单位售卖seller* s1 =new seller(&sem);s1->moveToThread(&t1);  //开辟新的线程去执行  槽函数也是在新的线程执行seller* s2 =new seller(&sem);s2->moveToThread(&t2);seller* s3 =new seller(&sem);s3->moveToThread(&t3);//因为class类this 是s对象s1->setObjectName("Seller1");s2->setObjectName("Seller2");s3->setObjectName("Seller3");QObject::connect(&t1,&QThread::started,s1,&seller::display);QObject::connect(&t2,&QThread::started,s2,&seller::display);QObject::connect(&t3,&QThread::started,s3,&seller::display);QObject::connect(s1,&seller::emptyTicket,&t1,&QThread::quit);QObject::connect(s2,&seller::emptyTicket,&t2,&QThread::quit);QObject::connect(s3,&seller::emptyTicket,&t3,&QThread::quit);QObject::connect(s1,&seller::emptyTicket,s1,&seller::deleteLater);QObject::connect(s2,&seller::emptyTicket,s2,&seller::deleteLater);QObject::connect(s3,&seller::emptyTicket,s3,&seller::deleteLater);QObject::connect(&t1,&QThread::finished,&t1,&QThread::deleteLater);QObject::connect(&t2,&QThread::finished,&t2,&QThread::deleteLater);QObject::connect(&t3,&QThread::finished,&t3,&QThread::deleteLater);t1.start();t2.start();t3.start();// 等待所有线程完成t1.wait();t2.wait();t3.wait();return a.exec();
}

 QWaitCondition 条件变量

QWaitCondition 是 Qt 框架中用于线程间同步的类之一。它允许一个线程等待另一个线程发出信号,从而实现线程间的协调和同步。 (相当于c11的条件变量)

        在使用 QWaitCondition 时,通常会配合使用 QMutex。QMutex 用于保护共享资源,而 QWaitCondition 则用于在等待某个条件为真时挂起线程,并在条件满足时唤醒线程。

成员函数 

void wait(QMutex *mutex):
将当前线程挂起,并释放指定的 QMutex。当某个其他线程调用 wakeOne() 或 wakeAll() 时,该线程将被唤醒。
当线程被唤醒时,它会重新获取 QMutex。bool wait(QMutex *mutex, unsigned long time):
与上一个函数类似,但是带有超时时间参数。如果在指定的时间内没有被唤醒,该函数将返回 false。
返回 true 表示正常被唤醒,返回 false 表示超时。void wakeOne():
唤醒一个正在等待的线程。如果没有线程正在等待,该函数什么也不做。
当有多个线程在等待时,哪个线程被唤醒是不确定的。void wakeAll():
唤醒所有正在等待的线程。
所有等待的线程都将被唤醒,并重新获取相关的 QMutex。

基于条件遍历实现一个售票店与制票商供应程序

#ifndef PRODUCWORKER_H
#define PRODUCWORKER_H#include"SellerWorker.h"//模拟生产
class ProducWorker : public QObject {Q_OBJECT
private:QQueue<int> *curTicket;QMutex* mtx;QWaitCondition* cond;int *curTicketid ;
public:ProducWorker()=default;ProducWorker(QQueue<int> *curTicket,QMutex* mtx,QWaitCondition* cond,int *curTicketid){this->curTicket=curTicket;this->mtx=mtx;this->cond=cond;this->curTicketid=curTicketid;}~ProducWorker()=default;
public slots:void work() {while (1) {{QMutexLocker lock(mtx);if (curTicket->length() < 20) { //假设最大生产20张票int data=++(*curTicketid);qDebug() << "生产票:" <<data ;curTicket->push_back(data);cond->wakeAll(); //通知商店售票一张} else {cond->wait(mtx); //超过则等待销售}QThread::usleep(1000);}}}
};
#endif // PRODUCWORKER_H
#ifndef SELLERWORKER_H
#define SELLERWORKER_H
#include <QWaitCondition>
#include <QThread>
#include <QMutex>
#include <QQueue>
#include <QDebug>//模拟售票
class SellerWorker : public QObject {Q_OBJECT
private:QQueue<int> *curTicket;QMutex* mtx;QWaitCondition* cond;int *curTicketid ;
public:SellerWorker()=default;SellerWorker(QQueue<int> *curTicket,QMutex* mtx,QWaitCondition* cond,int *curTicketid){this->curTicket=curTicket;this->mtx=mtx;this->cond=cond;this->curTicketid=curTicketid;}~SellerWorker()=default;
public slots:void work() {while (1) {{QMutexLocker lock(mtx);if (!curTicket->isEmpty()) {qDebug() << "售票:" << curTicket->front();curTicket->pop_front();cond->wakeOne(); //通知生产者 生产一张} else {cond->wait(mtx);}QThread::usleep(1000);}}}
};
#endif // SELLERWORKER_H
#include <QCoreApplication>
#include"ProducWorker.h"
#include"ProducWorker.h"int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);QQueue<int> curTicket;QMutex mtx;QWaitCondition cond;int curTicketid =0;QThread t1, t2;SellerWorker *sellerWorker =new SellerWorker(&curTicket,&mtx,&cond,&curTicketid);ProducWorker *producWorker =new ProducWorker(&curTicket,&mtx,&cond,&curTicketid);sellerWorker->moveToThread(&t1);producWorker->moveToThread(&t2);QObject::connect(&t1, &QThread::started, sellerWorker, &SellerWorker::work);QObject::connect(&t2, &QThread::started, producWorker, &ProducWorker::work);t1.start();t2.start();return a.exec();
}

QReadWriteLock 读写锁

        QReadWriteLock 是 Qt 提供的用于读写操作的锁类,允许多个线程同时读取共享数据,提高了并发性能,但在写操作时会阻止其他的读取和写入,以确保数据的一致性。

void lockForRead():
获取读锁。如果已有写锁被持有,当前线程将被阻塞,直到获得读锁。
可以被多个线程同时调用,只要没有写锁被持有。
void lockForWrite():
获取写锁。如果已有读锁或写锁被持有,当前线程将被阻塞,直到获得写锁。
一次只允许一个线程持有写锁。
void unlock():
释放当前持有的读锁或写锁。
如果有其他线程正在等待锁,它们将被唤醒。
bool tryLockForRead(int timeout = 0):
尝试获取读锁。如果成功获取,返回 true。
如果在指定的超时时间内无法获取读锁,返回 false。
bool tryLockForWrite(int timeout = 0):
尝试获取写锁。如果成功获取,返回 true。
如果在指定的超时时间内无法获取写锁,返回 false。

 基于读写锁实现3个写线程和5个读线程的同步

#ifndef CACHEMANAGER_H
#define CACHEMANAGER_H
#include <QDebug>
#include <QReadWriteLock>
#include <QThread>
#include <QVector>class CacheManger : public QObject
{Q_OBJECT
public:CacheManger() {}virtual ~CacheManger() {}CacheManger( QVector<int> * data,QReadWriteLock* rwLock) :m_data(data),m_rwLock(rwLock){}void readData(int index) {while(1){m_rwLock->lockForRead();qDebug() << "Reading data at index:" << index << "- Data:" << m_data->at(index);m_rwLock->unlock();}}void writeData(int &sum) { //sumint &value = sum;while (1) {m_rwLock->lockForWrite();value+=1;int index =value % 5;(*m_data)[index] = value;qDebug() << "Writing data at index:" << index << "- Data:" << value;m_rwLock->unlock();QThread::usleep(1000);}}
private:QVector<int> * m_data ;QReadWriteLock* m_rwLock;
};
#endif // CACHEMANAGER_H
#include <QCoreApplication>
#include"CacheManager.h"int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);QVector<int> m_data = {1, 2, 3, 4, 5};QReadWriteLock m_rwLock;for(int i=0;i<5;i++){QThread* t =new QThread;CacheManger* read = new CacheManger(&m_data,&m_rwLock);read->moveToThread(t);QObject::connect(t,&QThread::started,read,[read,i](){read->readData(i);});t->start();}int sum=0;for(int i=0;i<3;i++){QThread* t =new QThread;CacheManger* write = new CacheManger(&m_data,&m_rwLock);write->moveToThread(t);QObject::connect(t,&QThread::started,write,[write,&sum](){  //3个线程对缓冲区循环写入数据write->writeData(sum);});t->start();}return a.exec();
}

总结

        上述展示了Qt多线程编程的基本知识,与C语言、C++等语言线程大体上一致。线程间的同步和互斥涉及到的知识点大体不差。Qt还可以通过信号和槽连接,在不同线程之间进行通信和同步。Qt多线程编程更需要关注的点是线程的生命周期以及如何更加优雅的退出线程。Qt除了线程还提供了一些模块、线程池等功能和机制来实现并发执行。

 参考文献:
       一文搞定之Qt多线程(QThread、moveToThread)_qthread movetothread-CSDN博客

最后附上源代码链接
对您有帮助的话,帮忙点个star

37-Qmutex-QSempahor · jbjnb/Qt demo - 码云 - 开源中国 (gitee.com)

38-QSempahor · jbjnb/Qt demo - 码云 - 开源中国 (gitee.com)

39-QWaitCondition · jbjnb/Qt demo - 码云 - 开源中国 (gitee.com)

40-QReadWriteLock · jbjnb/Qt demo - 码云 - 开源中国 (gitee.com)

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

uniapp+vue3嵌入Markdown格式

使用的库是towxml 第一步&#xff1a;下载源文件&#xff0c;那么可以git clone&#xff0c;也可以直接下载压缩包 git clone https://github.com/sbfkcel/towxml.git 第二步&#xff1a;设置文件夹内的config.js&#xff0c;可以选择自己需要的格式 第三步&#xff1a;安装…

人与机器的协同是强弱系统的互补行为

人与机器的协同可以被视作强弱系统的互补行为&#xff0c;这也强调了人类和机器之间在处理问题、执行任务或创造价值时各自的优势与角色。 人类在认知、创造力、情感和伦理等方面具有独特优势。我们能够进行高级的抽象思维、创新和复杂决策&#xff0c;能够处理不确定性和动态环…

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第一篇 嵌入式Linux入门篇-第十二章 Linux 权限管理

i.MX8MM处理器采用了先进的14LPCFinFET工艺&#xff0c;提供更快的速度和更高的电源效率;四核Cortex-A53&#xff0c;单核Cortex-M4&#xff0c;多达五个内核 &#xff0c;主频高达1.8GHz&#xff0c;2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…

Camera Raw:常规工具

在 Camera Raw 窗口右下角提供了四个常用的工具&#xff0c;它们分别是&#xff1a;缩放工具、抓手工具、切换取样器叠加以及切换网格叠加工具。 ◆ ◆ ◆ 缩放工具 Zoom Tool 用于放大或缩小预览图像&#xff0c;便于查看和编辑细节。 快捷键&#xff1a;Z 1、双击“缩放工具…

一、YOLO V10安装、使用、训练大全

YOLO V10安装、使用、训练大全 一、下载官方源码二、配置conda环境三、安装YOLOV10依赖四、使用官方YOLO V10模型1.下载模型2.使用模型2.1 图片案例 五、制作数据集1.数据集目录结构2.标注工具2.1 安装标注工具2.2 运行标注工具2.3 设置自动保存2.4 切换yolo模式2.5 开始标注2.…

VBA实现Excel数据排序功能

前言 本节会介绍使用VBA如何实现Excel工作表中数据的排序功能。 本节会通过下表数据内容为例进行实操&#xff1a; 1. Sort 单列排序 语法&#xff1a;Sort key1,Order1 说明&#xff1a; Key1&#xff1a;表示需要按照哪列进行排序 Order1&#xff1a;用来指定是升序xlAsce…

学习笔记——动态路由——IS-IS中间系统到中间系统(IS-IS工作过程)

六、IS-IS工作过程 1、第一步&#xff1a;建立邻居关系 IS-IS网络中所有路由器之间实现通信&#xff0c;主要通过以下几个步骤&#xff1a; (1)邻居关系建立&#xff1a; 邻居关系建立主要是通过HELLO包交互并协商各种参数&#xff0c;包括链路类型(level-1/level-2)&#…

密态计算,大模型商用数据瓶颈的新解法?

大数据产业创新服务媒体 ——聚焦数据 改变商业 大模型迈向产业的深度应用&#xff0c;首要挑战是高质量数据供给和安全流通。正如在今年的世界人工智能大会上&#xff0c;产学研届多位专家达成的共识是&#xff0c;数据决定了AI能力的上限。 在实践中&#xff0c;行业大模型难…

11、Python之变量:看得见还是看不见

引言 在前面一篇关于Python变量的文章中&#xff0c;更多地结合对象的内存结构及字节码指令&#xff0c;来看不同代码针对不同的类型的对象的不同效果。 今天这篇文章中&#xff0c;想对新手在使用Python变量中&#xff0c;可能遇到的其他困惑&#xff0c;再展开来说一下。 大…

抖音机构号授权矩阵系统源码:构建创新的社交媒体平台

抖音机构号授权矩阵系统源码是一个为抖音机构号提供授权管理的系统。该系统前端使用了uni-app和vue作为开发框架&#xff0c;后端采用了ThinkPHP5、wokerman和ElementUI。同时&#xff0c;剪辑版块使用了阿里云智能媒体服务和阿里云对象存储来实现。 抖音机构号授权矩阵系统源码…

读人工智能全传08人工智能的今天

1. 人工智能的今天 1.1. 未来&#xff0c;或许有些领域会有非常明显的人工智能痕迹&#xff0c;有些领域则不会 1.2. 2018年&#xff0c;来自计算机视觉处理器公司英伟达的研究人员证明了人工智能软件能够创造出虚假的人物照片&#xff0c;并且能够完全…

Сетунь的24条单播指令

1、Setun模拟器概述 真的&#xff0c;想搞懂一台电脑是怎么运行的&#xff0c;那就搞懂它的指今集是怎么跑的&#xff0c;感觉很离了个大谱的&#xff0c;先看由铁氧体磁芯上的器件组成的RAM&#xff0c;容量为162个9-trit单元&#xff0c;即每个单元为9-trit&#xff0c;每页有…

学习嵌入式对于学历有要求吗?

学习嵌入式系统开发通常并不对学历有严格的要求&#xff0c;尤其是在技术行业中&#xff0c;实际的技能和经验往往比学历更为重要。我收集归类了一份嵌入式学习包&#xff0c;对于新手而言简直不要太棒&#xff0c;里面包括了新手各个时期的学习方向编程教学、问题视频讲解、毕…

Python中JSON处理技术的详解

引言 JSON&#xff08;JavaScript Object Notation&#xff09;作为当前最流行的数据传输格式&#xff0c;在Python中也有多种实现方式。由于JSON的跨平台性和简便易用性&#xff0c;它在数据交互中被广泛应用。本文将重点讨论如何熟练应用Python的JSON库&#xff0c;将JSON数…

Vagrant配合VirtualBox搭建虚拟机

目录 前言一、软件下载及安装1.下载2.安装扩展&#xff1a; 二、创建一个虚拟机1.Vagrant官方镜像仓库 三、使用远程工具连接虚拟机1.修改相关配置文件 四、虚拟机克隆及使用1.通用配置2.简单搭建一个java环境3.克隆虚拟机1.重命名虚拟机&#xff08;可选&#xff09;2.打包指定…

计算机视觉研究方向初学习,计算机视觉都有什么方向??!到底是干什么的?!

计算机视觉研究方向初学习&#xff0c;计算机视觉都有什么方向&#xff1f;&#xff1f;&#xff01;到底是干什么的&#xff1f;&#xff01; 语义分割图像分类目标检测和定位实例分割、全景分割物体跟踪姿态估计人脸识别人体识别图像增强风格迁移图像生成视觉问答视频分析光学…

大话光学原理:4.散射:瑞利、拉曼、米氏和布里渊

这是一缕柔和的光&#xff0c;在空气的舞台上轻盈地跳跃。它悠然自得&#xff0c;在宁静的空间中缓缓前行。然而&#xff0c;一片细薄透明的介质挡住了它的脚步&#xff0c;它毫无预兆地撞上了这片障碍。在这短暂的接触中&#xff0c;它被分解成无数微小的粒子&#xff0c;被迫…

【总线】AXI第九课时:介绍AXI响应信号 (Response Signaling):RRESP和 BRESP

大家好,欢迎来到今天的总线学习时间!如果你对电子设计、特别是FPGA和SoC设计感兴趣&#xff0c;那你绝对不能错过我们今天的主角——AXI4总线。作为ARM公司AMBA总线家族中的佼佼者&#xff0c;AXI4以其高性能和高度可扩展性&#xff0c;成为了现代电子系统中不可或缺的通信桥梁…

Python | Leetcode Python题解之第226题翻转二叉树

题目&#xff1a; 题解&#xff1a; class Solution:def invertTree(self, root: TreeNode) -> TreeNode:if not root:return rootleft self.invertTree(root.left)right self.invertTree(root.right)root.left, root.right right, leftreturn root

NLP入门——卷积语言模型的搭建、训练与预测

语言模型建模是针对句子建模&#xff0c;主要分为掩码语言模型和自回归语言模型。 我们从corpus中截取一句话作为例子&#xff0c;这句话是bpe分词后的句子&#xff1a; 1994 年 5 月 17 日 安全 理事会 第 33 77 次 会议 通过 掩码语言模型的主要机制是&#xff1a;例如将33 7…