13-4_Qt 5.9 C++开发指南_基于QWaitCondition 的线程同步_Wait
在多线程的程序中,多个线程之间的同步实际上就是它们之间的协调问题。例如上一小节讲到的3个线程的例子中,假设 threadDAQ 写满一个缓冲区之后,threadShow 和 threadSaveFile 才能对缓冲区进行读操作。前面采用的互斥量和基于 OReadWriteLock 的方法都是对资源的锁定和解锁,避免同时访问资源时发生冲突。在一个线程解锁资源后,不能及时通知其他线程。(此处类似于C++中的条件变量,具体可参考:C++新特性36_条件变量的使用(介绍C++11中条件变量的用法;条件变量必须搭配互斥体和条件使用;条件、条件变量、互斥体三胞胎需要同时使用))
QWaitCondition 提供了另外一种改进的线程同步方法,QWaitCondition 与QMutex 结合,可以使一个线程在满足一定条件时通知其他多个线程,使它们及时作出响应,这样比只使用互斥量效率要高一些。例如,threadDAQ 在写满一个缓冲区之后,及时通知 threadShow 和threadSaveFile,使它们可以及时读取缓冲区数据。
QWaitCondition 提供如下一些函数:
- wait(QMutex *lockedMutex),解锁互斥量 lockedMutex,并阻塞等待唤醒条件,被唤醒后锁定lockedMutex 并退出函数;
- wakeAll(),唤醒所有处于等待状态的线程,线程唤醒的顺序不确定,由操作系统的调度策略决定:
- wakeOne(),唤醒一个处于等待状态的线程,唤醒哪个线程不确定,由操作系统的调度策略决定。
QWaitCondition 一般用于“生产者/消费者”(producer/consumer)模型
中。“生产者”产生数据,“消费者”使用数据,前述的数据采集、显示与存储的三线程例子就适用这种模型。
文章目录
- 1. 实例分析
- 2. 源码
- 2.1 dialog.h
- 2.2 dialog.cpp
- 2.3 qmythread.h
- 2.4 qmythread.cpp
1. 实例分析
创建实例程序 samp13_4,将掷骰子的程序修改为 producer/consumer 模型
,一个线程类QThreadProducer 专门负责掷骰子产生点数:一个线程类QThreadConsumer 专门及时读取数据,并送给主线程进行显示。这两个类定义在一个文件 qmythread.h 里,定义代码如下:
#ifndef QMYTHREAD_H
#define QMYTHREAD_H//#include <QObject>
#include <QThread>class QThreadProducer : public QThread
{Q_OBJECT
private:bool m_stop=false; //停止线程
protected:void run() Q_DECL_OVERRIDE;
public:QThreadProducer();void stopThread();
};class QThreadConsumer : public QThread
{Q_OBJECT
private:bool m_stop=false; //停止线程
protected:void run() Q_DECL_OVERRIDE;
public:QThreadConsumer();void stopThread();
signals:void newValue(int seq,int diceValue);
};
#endif // QMYTHREAD_H
QThreadProducer用于掷骰子,但是去掉了开始和暂停的功能,线程一启动就连续地掷骰子。QThreadConsumer用于读取掷骰子的次数和点数,并用发射信号方式把数据传递出去。这两个类的实现代码在一个文件qmythread.cpp里,下面是这两个类的实现代码的主要部分:
QMutex mutex;
QWaitCondition newdataAvailable;int seq=0;//序号
int diceValue;void QThreadProducer::run()
{m_stop=false;//启动线程时令m_stop=falseseq=0;qsrand(QTime::currentTime().msec());//随机数初始化,qsrand是线程安全的while(!m_stop)//循环主体{mutex.lock();diceValue=qrand(); //获取随机数diceValue=(diceValue % 6)+1;seq++;mutex.unlock();newdataAvailable.wakeAll();//唤醒所有线程,有新数据了msleep(500); //线程休眠100ms}
}void QThreadConsumer::run()
{m_stop=false;//启动线程时令m_stop=falsewhile(!m_stop)//循环主体{mutex.lock();newdataAvailable.wait(&mutex);//会先解锁mutex,使其他线程可以使用mutexemit newValue(seq,diceValue);mutex.unlock();
// msleep(100); //线程休眠100ms}}
掷骰子的次数和点数的变量定义为共享变量,这样两个线程都可以访问。定义了互斥量mutex,定义了QWaitCondition实例newdataAvailable,表示有新数据可用了。
QThreadProducer.:run()函数负责每隔 500 毫秒掷骰子产生一次数据,新数据产生后通过等待条件唤醒所有等待的线程,即:
newdataAvailable.wakeAll();//唤醒所有线程,有新数据了
QThreadConsumer::run()函数中的while循环,首先需要将互斥量锁定,再执行下面的一条语句:
newdataAvailable.wait(&mutex);//会先解锁mutex,使其他线程可以使用mutex
这条语句以 mutex 作为输入参数,内部会首先解锁 mutex,使其他线程可以使用 mutex,newdataAvailable 进入等待状态。当 QThreadProducer 产生新数据使用 newdataAvailable.wakeAll()唤醒所有线程后,newdataAvailablewait(&mutex)会再次锁定 mutex,然后退出阻塞状态,以执行后面的语句。
所以,使用 QWaitCondition 可以使 QThreadConsumer 线程的执行过程进入等待状态。在QThreadProducer 线程满足条件后,唤醒QThreadConsumer 线程及时退出等待状态,继续执行后面的程序。
使用QThreadProducer和QThreadConsumer 实现掷骰子的实例程序samp13_4运行时界面如图13-2 所示,与实例 samp13_1 的运行界面类似,只是取消了开始和暂停掷假子的按钮,下方的状态标签显示了两个线程的状态。
窗口的 Dialog 类的定义如下(省略了按钮槽函数等一些不重要的部分):
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QTimer>#include "qmythread.h"namespace Ui {
class Dialog;
}class Dialog : public QDialog
{Q_OBJECTprivate:QThreadProducer threadProducer;QThreadConsumer threadConsumer;
protected:void closeEvent(QCloseEvent *event);
public:explicit Dialog(QWidget *parent = 0);~Dialog();private slots:void onthreadA_started();void onthreadA_finished();void onthreadB_started();void onthreadB_finished();void onthreadB_newValue(int seq, int diceValue);...private:Ui::Dialog *ui;
};#endif // DIALOG_H
这里主要是定义了两个线程的实例,并定义了几个自定义槽函数。采用信号与槽的方式与threadConsumer建立通信并获取数据。Dialog 的构造函数主要完成信号与槽函数的关联,5 个自定义槽函数的代码与实例 samp13_1 的相同或相似,这几个函数的代码不再详细列出。
"启动线程"按钮的代码如下:
void Dialog::on_btnStartThread_clicked()
{//启动线程threadConsumer.start();threadProducer.start();ui->btnStartThread->setEnabled(false);ui->btnStopThread->setEnabled(true);
}
两个线程启动的先后顺序不应调换,应先启动 threadConsumer,使其先进入 wait 状态,后启动 threadProducer,这样在threadProducer 里 wakeAll()时 threadConsumer 就可以及时响应,否则会丢失一次掷骰子的数据。
“结束线程”按钮的代码如下:
void Dialog::on_btnStopThread_clicked()
{//结束线程threadProducer.stopThread();//结束线程的run()函数执行threadProducer.wait();//// threadConsumer.stopThread();//结束线程的run()函数执行threadConsumer.terminate(); //因为threadB可能处于等待状态,所以用terminate强制结束threadConsumer.wait();//ui->btnStartThread->setEnabled(true);ui->btnStopThread->setEnabled(false);
}
结束线程时,若按照上面的顺序先结束 threadProducer 线程,则必须使用terminate()来强制结束threadConsumer 线程,因为 threadConsumer 可能还处于条件等待的阻塞状态中,将无法正常结束线程。
2. 源码
2.1 dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QTimer>#include "qmythread.h"namespace Ui {
class Dialog;
}class Dialog : public QDialog
{Q_OBJECTprivate:QThreadProducer threadProducer;QThreadConsumer threadConsumer;
protected:void closeEvent(QCloseEvent *event);
public:explicit Dialog(QWidget *parent = 0);~Dialog();private slots:void onthreadA_started();void onthreadA_finished();void onthreadB_started();void onthreadB_finished();void onthreadB_newValue(int seq, int diceValue);void on_btnClear_clicked();void on_btnStopThread_clicked();void on_btnStartThread_clicked();private:Ui::Dialog *ui;
};#endif // DIALOG_H
2.2 dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"void Dialog::closeEvent(QCloseEvent *event)
{//关闭窗口if (threadProducer.isRunning()){threadProducer.stopThread();threadProducer.wait();}if (threadConsumer.isRunning()){threadConsumer.terminate(); //因为threadB可能处于等待状态,所以用terminate强制结束threadConsumer.wait();//}event->accept();
}Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog)
{ui->setupUi(this);connect(&threadProducer,SIGNAL(started()),this,SLOT(onthreadA_started()));connect(&threadProducer,SIGNAL(finished()),this,SLOT(onthreadA_finished()));connect(&threadConsumer,SIGNAL(started()),this,SLOT(onthreadB_started()));connect(&threadConsumer,SIGNAL(finished()),this,SLOT(onthreadB_finished()));connect(&threadConsumer,SIGNAL(newValue(int,int)),this,SLOT(onthreadB_newValue(int,int)));
}Dialog::~Dialog()
{delete ui;
}void Dialog::onthreadA_started()
{ui->LabA->setText("Thread Producer状态: started");
}void Dialog::onthreadA_finished()
{ui->LabA->setText("Thread Producer状态: finished");
}void Dialog::onthreadB_started()
{ui->LabB->setText("Thread Consumer状态: started");
}void Dialog::onthreadB_finished()
{ui->LabB->setText("Thread Consumer状态: finished");
}void Dialog::onthreadB_newValue(int seq,int diceValue)
{QString str=QString::asprintf("第 %d 次掷骰子,点数为:%d",seq,diceValue);ui->plainTextEdit->appendPlainText(str);QPixmap pic;QString filename=QString::asprintf(":/dice/images/d%d.jpg",diceValue);pic.load(filename);ui->LabPic->setPixmap(pic);
}void Dialog::on_btnClear_clicked()
{ui->plainTextEdit->clear();
}void Dialog::on_btnStopThread_clicked()
{//结束线程threadProducer.stopThread();//结束线程的run()函数执行threadProducer.wait();//// threadConsumer.stopThread();//结束线程的run()函数执行threadConsumer.terminate(); //因为threadB可能处于等待状态,所以用terminate强制结束threadConsumer.wait();//ui->btnStartThread->setEnabled(true);ui->btnStopThread->setEnabled(false);
}void Dialog::on_btnStartThread_clicked()
{//启动线程threadConsumer.start();threadProducer.start();ui->btnStartThread->setEnabled(false);ui->btnStopThread->setEnabled(true);
}
2.3 qmythread.h
#ifndef QMYTHREAD_H
#define QMYTHREAD_H//#include <QObject>
#include <QThread>class QThreadProducer : public QThread
{Q_OBJECT
private:bool m_stop=false; //停止线程
protected:void run() Q_DECL_OVERRIDE;
public:QThreadProducer();void stopThread();
};class QThreadConsumer : public QThread
{Q_OBJECT
private:bool m_stop=false; //停止线程
protected:void run() Q_DECL_OVERRIDE;
public:QThreadConsumer();void stopThread();
signals:void newValue(int seq,int diceValue);
};
#endif // QMYTHREAD_H
2.4 qmythread.cpp
#include "qmythread.h"
#include <QWaitCondition>
#include <QTime>
#include <QMutex>QMutex mutex;
QWaitCondition newdataAvailable;int seq=0;//序号
int diceValue;QThreadProducer::QThreadProducer()
{}void QThreadProducer::stopThread()
{QMutexLocker locker(&mutex);m_stop=true;
}void QThreadProducer::run()
{m_stop=false;//启动线程时令m_stop=falseseq=0;qsrand(QTime::currentTime().msec());//随机数初始化,qsrand是线程安全的while(!m_stop)//循环主体{mutex.lock();diceValue=qrand(); //获取随机数diceValue=(diceValue % 6)+1;seq++;mutex.unlock();newdataAvailable.wakeAll();//唤醒所有线程,有新数据了msleep(500); //线程休眠100ms}
}void QThreadConsumer::run()
{m_stop=false;//启动线程时令m_stop=falsewhile(!m_stop)//循环主体{mutex.lock();newdataAvailable.wait(&mutex);//会先解锁mutex,使其他线程可以使用mutexemit newValue(seq,diceValue);mutex.unlock();
// msleep(100); //线程休眠100ms}}QThreadConsumer::QThreadConsumer()
{}void QThreadConsumer::stopThread()
{QMutexLocker locker(&mutex);m_stop=true;
}
相关文章:

13-4_Qt 5.9 C++开发指南_基于QWaitCondition 的线程同步_Wait
在多线程的程序中,多个线程之间的同步实际上就是它们之间的协调问题。例如上一小节讲到的3个线程的例子中,假设 threadDAQ 写满一个缓冲区之后,threadShow 和 threadSaveFile 才能对缓冲区进行读操作。前面采用的互斥量和基于 OReadWriteLock…...

STM32(HAL)多串口进行重定向(printf函数发送数据)
目录 1、简介 2.1 基础配置 2.1.1 SYS配置 2.1.2 RCC配置 2.2 串口外设配置 2.3 项目生成 3、KEIL端程序整合 4、效果测试 1、简介 在HAL库中,常用的printf函数是无法使用的。本文通过重映射实现在HAL库多个串口可进行类似printf函数的操作。 2.1 基础配置 2.…...

29_互联网(The Internet)(IP数据包;UDP;TCP;DNS;OSI)
上篇介绍了计算机网络的基础知识,也提到互联网(The Internet),本篇将会详细介绍互联网(The Internet)。 文章目录 1. 互联网(The Internet)组成及数据包传输过程2. IP 数据包的不足3…...
xShell常用命令
xShell常用命令 一、文件夹目录1、cd-更改目录2、mkdir-建立目录3、rm-删除目录4、pwd-查看当前路径5、rmdir-删除空目录 二、文件操作1、cat-显示文件内容2、diff-比较文件内容3、查看文件的名字和后缀4、ls-列出文件5、cp-复制文件6、mv-移动和重命名文件找不同:选…...
React性能优化之Memo、useMemo
文章目录 React.memo两种方式参数应用场景 拓展useMemouseMemo(calculateValue, dependencies) 参考资料 React.memo React 的渲染机制,组件内部的 state 或者 props 一旦发生修改,整个组件树都会被重新渲染一次,即时子组件的参数没有被修改&…...

IDEA开启并配置services窗口
前言: 一般一个spring cloud项目中大大小小存在几个十几个module编写具体的微服务项目。此时,如果要调试测需要依次启动各个项目比较麻烦。 方法一: 默认第一次打开项目的时候,idea会提示是否增加这个选项卡,如果你没…...
vue2企业级项目(三)
vue2企业级项目(三) 引入mockjs,i18n 1、mockjs 项目下载依赖 npm install --save-dev mock根目录创建mock文件夹,并创建mock/index.js import Mock from "mockjs";// 设置全局延时 没有延时的话有时候会检测不到数据…...

QT 在label上透明绘图
一、新建TransparentDemo工程 二、在界面上添加label,修改样式表,将底色置为红色,作为北京 三、新建一个TransparentLabel类,继承自QLabel 此时,工程包括文件 五、在transparentlabel.h中添加 头文件 #include …...

SAM(Segment Anything)大模型论文汇总
A Comprehensive Survey on Segment Anything Model for Vision and Beyond 论文:https://arxiv.org/abs/2305.08196 25页综述,198篇参考文献!52个开源项目!本文第一个全面回顾了分割一切模型(SAM)的研究和应用进展,…...

金融翻译难吗,如何做好金融翻译?
我们知道,金融翻译涉及企业经济这块的,是影响各公司发展很重要的一方面,翻译做得好,可以促进公司内外的交流,及时掌握各种信息,做好应对。那么,金融翻译难吗,如何做好金融翻译&#…...
Java面试题(Tomcat与Nginx)
Tomcat 什么是Tomcat? 简单来说是一个运行Java的网络服务器,也是jsp和serlvet的一个容器 Tomcat的缺省端口是多少,怎么修改? conf文件夹下修改server.xml文件 <Connector connectionTimeout"20000" port"8080" p…...
React-使用mobx
React 中使用 mobx 配置开发环境 安装mobx和中间件工具 mobx-react-lite 只能函数组件中使用 yarn add mobx mobx-react-lite初始化 mobx 定义数据状态 state在构造器中实现数据响应式处理 makeAutoObservble定义修改数据的函数 action实例化 store 并导出 import { compute…...
LeetCode ACM模式——哈希表篇(一)
刷题顺序及部分思路来源于代码随想录,网站地址:https://programmercarl.com 部分思路来源于力扣官方题解,作者主页:https://leetcode.cn/u/leetcode-solution/ 242. 有效的字母异位词 给定两个字符串 s 和 t ,编写一个…...
WPF实战学习笔记31-登录界面全局通知
UI添加消息聚合器 <md:Snackbarx:Name"LoginSnakeBar"Grid.ColumnSpan"2"Panel.ZIndex"1"MessageQueue"{md:MessageQueue}" />注册提示消息 文件:Mytodo.Views.LoginView.cs构造函数添加内容 //注册提示消息 aggre…...

通用商城项目(中)
金山编译器出问题了。下面段落标号全出问题了,排版也出问题了。懒得改了。 使用对象存储OSS,保存品牌logo 新建Module,提供上传、显示服务 有些不明所以的,按照steinliving-commodity配置了一通pom.xml 新建application.yml&…...
谨慎使用JSON.stringify
谨慎使用JSON.stringify 为了避免因为对象是引用类型而造成的数据源污染,我们通常使用 JSON.stringify 将其转换为字符串,而后通过JSON.parse方法将字符串转化一个新对象来实现深拷贝。但是在这个过程中也会存在一些问题,本文就介绍一下使用…...
驱动开发day8
编写LED灯的驱动,使用GPIO子系统,里面添加按键的中断处理 1.应用程序发送指令控制LED亮灭 2.按键1 按下,led1电位反转 按键2按下,led2电位反转 按键3 按下,led3电位反转 驱动程序 #include <linux/init.h> #i…...

CAS 机制
问题分析与思考: CAS 是 Java 中 Unsafe 类里面的方法,它的全称是 CompareAndSwap,比较并交换 的意思。 它的主要功能是能够保证在多线程环境下,对于共享变量的修改的原子性。 举个例子,比如说有这样一个场景ÿ…...
#P1003. [NOIP2009普及组] 道路游戏
题目描述 小新正在玩一个简单的电脑游戏。 游戏中有一条环形马路,马路上有 nn 个机器人工厂,两个相邻机器人工厂之间由一小段马路连接。小新以某个机器人工厂为起点,按顺时针顺序依次将这 nn 个机器人工厂编号为 1\sim n1∼n,因…...

python-网络爬虫.regular
regular 正则表达式 (regular expression) 正则表达式(regular expression)描述了一种字符串匹配的模式 (pattern), 可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串 中取出符合某个条件的子串等。 正则表达式是由普通…...

UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...

汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...

认识CMake并使用CMake构建自己的第一个项目
1.CMake的作用和优势 跨平台支持:CMake支持多种操作系统和编译器,使用同一份构建配置可以在不同的环境中使用 简化配置:通过CMakeLists.txt文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本…...

基于单片机的宠物屋智能系统设计与实现(论文+源码)
本设计基于单片机的宠物屋智能系统核心是实现对宠物生活环境及状态的智能管理。系统以单片机为中枢,连接红外测温传感器,可实时精准捕捉宠物体温变化,以便及时发现健康异常;水位检测传感器时刻监测饮用水余量,防止宠物…...

如何把工业通信协议转换成http websocket
1.现状 工业通信协议多数工作在边缘设备上,比如:PLC、IOT盒子等。上层业务系统需要根据不同的工业协议做对应开发,当设备上用的是modbus从站时,采集设备数据需要开发modbus主站;当设备上用的是西门子PN协议时…...

spring boot使用HttpServletResponse实现sse后端流式输出消息
1.以前只是看过SSE的相关文章,没有具体实践,这次接入AI大模型使用到了流式输出,涉及到给前端流式返回,所以记录一下。 2.resp要设置为text/event-stream resp.setContentType("text/event-stream"); resp.setCharacter…...

【笔记】结合 Conda任意创建和配置不同 Python 版本的双轨隔离的 Poetry 虚拟环境
如何结合 Conda 任意创建和配置不同 Python 版本的双轨隔离的Poetry 虚拟环境? 在 Python 开发中,为不同项目配置独立且适配的虚拟环境至关重要。结合 Conda 和 Poetry 工具,能高效创建不同 Python 版本的 Poetry 虚拟环境,接下来…...

[10-1]I2C通信协议 江协科技学习笔记(17个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17...