百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

从零开始学Qt(81):基于QWaitCondition的线程同步

liebian365 2024-10-17 14:00 43 浏览 0 评论

QWaitCondition是什么?

在多线程的程序中,多个线程之间的同步实际上就是它们之间的协调问题。采用的互斥量和基于QReadWriteLock的方法都是对资源的锁定和解锁,避免同时访问资源时发生冲突。这些方法的缺点是,在一个线程解锁资源后,不能及时通知其他线程。

QWaitCondition提供了另外一种改进的线程同步方法,QWaitCondition与QMutex结合,可以使一个线程在满足一定条件时通知其他多个线程,使它们及时作出响应,这样比只使用互斥量效率要高一些。

QWaitCondition提供如下一些函数:

  • wait(QMutex *lockedMutex),解锁互斥量lockedMutex,并阻塞等待唤醒条件,被唤醒后锁定lockedMutex并退出函数;
  • wakeAll(),唤醒所有处于等待状态的线程,线程唤醒的顺序不确定,由操作系统的调度策略决定;
  • wakeOne(),唤醒一个处于等待状态的线程,唤醒哪个线程不确定,由操作系统的调度策略决定。

QWaitCondition 一般用于“生产者/消费者”(producer/consumer)模型中。“生产者”产生数据,“消费者”使用数据,上一篇文章中的数据采集、显示与存储的三线程例子就适用这种模型。

使用QWaitCondition定义掷骰子的线程

创建实例程序,将掷骰子的程序(参见前面文章:从零开始学Qt(77):多线程程序实例 - 投骰子从零开始学Qt(79):基于互斥量的线程同步 )修改为producer/consumer模型,一个线程类 QThreadProducer专门负责掷骰子产生点数;一个线程类QThreadConsumer专门及时读取数据,并送给主线程进行显示。这两个类定义在一个文件qmythread.h里,定义代码如下:

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);
};

QThreadProducer用于掷骰子,但是去掉了开始和暂停的功能,线程一启动就连续地掷骰子。 QThreadConsumer用于读取掷骰子的次数和点数,并用发射信号方式把数据传递出去。这两个类的实现代码在一个文件qmythread.h里,下面是这两个类的实现代码的主要部分:

void QThreadProducer::run()
{
  m_stop=false;
  seq=0;
  qsrand(QTime::currentTime().msec());//随机数初始化
  while(!m_stop){ //循环主体
    mutex.lock();
    diceValue=qrand(); //获取随机数
    diceValue=(diceValue % 6)+1;
    seq++;
    mutex.unlock();
    newdataAvailable.wakeAll(); //唤醒所有线程,有新数据了
    msleep(500); //线程休眠
  }
}

void QThreadConsumer::run()
{
  m_stop=false;
  while(!m_stop){ //循环主体
    mutex.lock();
    newdataAvailable.wait(&mutex); // 先解锁mutex,使其他线程可以使用mutex
    emit newValue(seq,diceValue);
    mutex.unlock();
  }
}

掷骰子的次数和点数的变量定义为共享变量,这样两个线程都可以访问。定义了互斥量 mutex,定义了 QWaitCondition 实例 newdataAvailable,表示有新数据可用了。

QThreadProducer::run()函数负责每隔500毫秒掷骰子产生一次数据,新数据产生后通过等待条件唤醒所有等待的线程,即:

newdataAvailable.wakeAll();

QThreadConsumer::run()函数中的while循环,首先需要将互斥量锁定,再执行下面的一条语句:

newdataAvailable.wait(&mutex);

这条语句以mutex作为输入参数,内部会首先解锁mutex,使其他线程可以使用mutex, newdataAvailable进入等待状态。当QThreadProducer产生新数据使用 newdataAvailable.wakeAll()唤醒所有线程后,newdataAvailable.wait(&mutex)会再次锁定mutex,然后退出阻塞状态,以执行后面的语句。

所以,使用QWaitCondition可以使QThreadConsumer线程的执行过程进入等待状态。在 QThreadProducer线程满足条件后,唤醒QThreadConsumer线程及时退出等待状态,继续执行后面的程序。

使用掷塞子的线程

使用QThreadProducer和QThreadConsumer实现掷骰子的实例程序运行时界面如图所示。下方的状态标签显示了两个线程的状态。

窗口的Widget类的定义如下(省略了按钮槽函数等一些不重要的部分):

class Widget : public QWidget
{
	Q_OBJECT
protected:
	void closeEvent(QCloseEvent *event);
private:
  QThreadProducer threadProducer;
  QThreadConsumer threadConsumer;
private slots:
//自定义槽函数
  void onthreadA_started();
  void onthreadA_finished();
  void onthreadB_started();
  void onthreadB_finished();
  void onthreadB_newValue(int seq, int diceValue);
};

这里主要是定义了两个线程的实例,并定义了几个自定义槽函数。采用信号与槽的方式与 threadConsumer建立通信并获取数据。Widget的构造函数主要完成信号与槽函数的关联,

Widget::Widget(QWidget *parent)
: QWidget(parent), ui(new Ui::Widget)
{//构造函数
  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)));
}

onthreadB_newValue是主线程用于接收和显示结果的槽函数,

void Widget::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/pic/d%d.jpg", diceValue);
  pic.load(filename);
  ui->labPic->setPixmap(pic);
}

其它4个自定义状态显示槽函数的代码与上一篇文章中实例的相同或相似,这几个函数的代码不再详细列出。

“启动线程”按钮的代码如下:

void Widget::on_btnStartThread_clicked()
{//启动线程按钮
  threadConsumer.start();
  threadProducer.start();
  ui->btnStartThread->setEnabled(false);
  ui->btnStopThread->setEnabled(true);
}

两个线程启动的先后顺序不应调换,应先启动threadConsumer,使其先进入wait状态,后启动threadProducer,这样在 threadProducer 里wakeAll()时threadConsumer就可以及时响应,否则会丢失第一次掷骰子的数据。

“结束线程”按钮的代码如下:

void Widget::on_btnStopThread_clicked()
{//结束线程按钮
  threadProducer.stopThread(); //结束线程的 run()函数执行
  threadProducer.wait();
  threadConsumer.terminate();//可能处于等待状态,用terminate强制结束
  threadConsumer.wait();
  ui->btnStartThread->setEnabled(true);
  ui->btnStopThread->setEnabled(false);
}

结束线程时,若按照上面的顺序先结束threadProducer线程,则必须使用terminate()来强制结束threadConsumer线程,因为threadConsumer可能还处于条件等待的阻塞状态中,将无法正常结束线程。

————————————————

觉得有用的话请关注点赞,谢谢您的支持!

对于本系列文章相关示例完整代码有需要的朋友,可关注并在评论区留言!

相关推荐

4万多吨豪华游轮遇险 竟是因为这个原因……

(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...

“菜鸟黑客”必用兵器之“渗透测试篇二”

"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...

科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白

作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...

麦子陪你做作业(二):KEGG通路数据库的正确打开姿势

作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...

知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势

智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...

每日新闻播报(September 14)_每日新闻播报英文

AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...

香港新巴城巴开放实时到站数据 供科技界研发使用

中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...

5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper

本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...

Qt动画效果展示_qt显示图片

今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...

如何从0到1设计实现一门自己的脚本语言

作者:dong...

三年级语文上册 仿写句子 需要的直接下载打印吧

描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...

C++|那些一看就很简洁、优雅、经典的小代码段

目录0等概率随机洗牌:1大小写转换2字符串复制...

二年级上册语文必考句子仿写,家长打印,孩子照着练

二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...

一年级语文上 句子专项练习(可打印)

...

亲自上阵!C++ 大佬深度“剧透”:C++26 将如何在代码生成上对抗 Rust?

...

取消回复欢迎 发表评论: