QT/C++多线程练习:单生产者多消费者(巨详细版本)

2023-05-16

QT多线程练习:单生产者多消费者

  • 代码思路
    • 1.全局变量
    • 2.消费者线程
    • 3.消费者管理类
    • 4.生产者线程
    • 5.主线程
    • 总结

最近在研究qt的线程的一些知识点,从如何新建线程到以不同的方式去新建线程,再到多线程以及多线程同步的几种方式等。
看了很多网上的demo最后都通过实现消费者和生产者模型来作为一个总结的练习。但是大部分帖子只是展示了代码和大致思路又或者在里面添加了一些内存和较难懂的线程指针操作,对新手很不友好读起来很费劲,所以自己完成了这个的demo后总结一下。

代码思路

先想好练习的demo是需要涉及 线程创建与退出、线程暂停、父子线程之前以及兄弟线程之间的参数和信号传递、多线程的以及多线程的管理。要求是练习的demo对于以上的点只要涉及基础即可。

  1. 先想好要创建哪些类:主线程、生产者线程(一)、消费者管理线程(一)、消费者线程(多),以及一些全局变量。
  2. 主线程职责:UI控件的控制和显示、控制生产者:调节生产的速度和暂停与继续、控制消费
  3. 生产者线程职责:根据商品数量调控生产,根据生产速度增加商品数量
  4. 消费者管理类:根据UI的控制去创建消费者线程以及删除/停止消费者线程,需要一个容器将已存在的消费者线程指针存储起来,并且可以通过容器内的指针去控制消费者线程。
  5. 消费者线程职责:减少商品数量,其他的都不管,全交给管理类去控制。
  6. 全局变量:
    int StoreSum;//商品库存
    Custom_sum;//消费者线程的数量
    QMutex Mutex;//互斥锁,防止消费者和生产者同时修改商品库存的值
    //其他变量在详细设计的时候再去添加
    项目文件

1.全局变量

单独把一些全局变量和共用的头文件放在appdata.h/.cpp 里面,这样在写的时候结构可以比较清晰,一般的成熟项目都这么写,严禁一点的还可以创建一个类把变量设置为私有的,再通过get/set去取值和修改。
appdata.h

#ifndef APPDATA_H
#define APPDATA_H

//全局变量和头文件
#include <QTime>
#include <QThread>
#include <QTimerEvent>
#include <QString>
#include <QDebug>
#include <QMutex>
#include <QList>

extern int StoreSum;//商品库存
extern int StoreSum_Max;//库存最大值
extern int StoreSum_Min;//库存最低警戒值
extern int Produce_speed;//当前生产的速度
extern int Produce_speedMax;//生产最大的速度
extern int Produce_speedMin;//生产最小的速度
extern int Custom_sum;//当前消费者的数量
extern QMutex Mutex;//锁,用来防止消费者线程和生产者线程同时修改商品库存的值
#endif // APPDATA_H

appdata.cpp

#include "appdata.h"
int StoreSum = 20;
int StoreSum_Max = 50;
int StoreSum_Min = 10;
int Produce_speed = 10;
int Produce_speedMax = 15;//生产最大的速度
int Produce_speedMin = 5;//生产最小的速度
int Custom_sum = 0;
QMutex Mutex;

2.消费者线程

消费者线程需要完成的事情比较单一,只需要获取全局变量中的StoreSum并且减1即可。

custom.h

#ifndef CUSTOM_H
#define CUSTOM_H
//如果要使用qdebug调试的话,需要加上QObject
#include <QObject>
#include "appdata.h"
//消费者线程要做的事情:
//每秒将商品数量-1
//其他的操作交给消费者管理类去控制
class Custom : public QThread
{
    Q_OBJECT
public:
    Custom();
    bool isRun;
protected:
    void run();
};
#endif // CUSTOM_H

custom.cpp

#include "custom.h"
Custom::Custom()
{
    isRun = 1;
}
void Custom::run()
{
    while(isRun)//线程start()后一直循环,通过isRun这个标志位去结束run()的循环
    {
    	//每次去StoreSum--之前先上锁防止与生产者同时去修改StoreSum
        Mutex.lock();
        //再根据设计逻辑去判断库存数量,低于StoreSum_Min就不再--
        //这里本来要发信号给主线程提示库存不足,但是因为中间隔了一个管理类 不知道为啥发不过去
        if(StoreSum>StoreSum_Min)
        {
            StoreSum--;
        }
        Mutex.unlock();
        sleep(1);
    }
}

3.消费者管理类

主线程通过消费者管理类去创建和退出消费者线程,相当于是一个专门管理的类。

custom.h

#ifndef CUSTOMMANAGER_H
#define CUSTOMMANAGER_H
#include "appdata.h"
#include "custom.h"
//消费者管理类
//1.创建消费者线程,并且把线程指针存到QList中
//2.通过QList中的线程指针去操控已存在的消费者线程,包括start和quit等。
class CustomManager
{
public:
    CustomManager();
    void creatCustom();//创建一个消费者
    void delCustom();//从消费者里面删除一个消费者
private:
    QList<Custom *> customList;//声明一个QList,里面存储的类型为Custom指针。这样就可以通过QList里面的项去直接操控线程
};
#endif // CUSTOMMANAGER_H

custom.cpp

#include "custommanager.h"
CustomManager::CustomManager()
{
}
//创建消费者线程并且添加到QList中去,通过QList去start()消费者线程
//需要去了解QList基础知识,插入以及取值等
void CustomManager::creatCustom()
{
    Custom *Customtemp = new Custom();          //创建一个临时的消费者线程
    customList.append(Customtemp);              //添加新的到尾节点
    customList.at(customList.size()-1)->start();//把刚刚添加到尾部的节点的线程开启

    Custom_sum = customList.size();             //更新全局变量Custom_sum
}

void CustomManager::delCustom()
{
    if(!customList.isEmpty())                  //非空判断,否则下面的操作操作无意义
    {
        customList.at(customList.size()-1)->isRun = 0;//先暂停run函数循环
        customList.at(customList.size()-1)->quit();   //结束并且退出线程等操作
        customList.at(customList.size()-1)->wait();
        delete customList.at(customList.size()-1);

        customList.removeLast();                      //QList删除尾节点
    }
    Custom_sum = customList.size();                   //更新全局变量Custom_sum
    qDebug()<<" now CustomSum =  "<<Custom_sum;
}

4.生产者线程

生产者线程是一直在运行的(run函数的循环不断),区别于消费者线程的创建和退出,生产者线程在修改商品数量之前是根据标志位商品数量满的调节去决定每一次的循环里面的是否要增加商品数量
主线程在构造函数里面就创建并start了生产者线程。
producer.h

#include "producer.h"
//构造函数是在创建对象的时候就运行了
Producer::Producer()
{
    isProduce = 1;
    connect(this,SIGNAL(stopProduce()),this,SLOT(stopProduce_slot()));
}
//run函数是需要start()才会运行,并且只运行一次,如果需要一直运行的话需要设置一个循环
//而如果要控制循环的话需要设置一个标志位去控制
void Producer::run()
{
    for(int i = 0;i<10000;i++)//怕运行出错导致卡死,先定一个大概的循环数量
    {
        Mutex.lock();
        if(isProduce)//先判断标志位
        {
            if(StoreSum<StoreSum_Max)//再判断库存数量是否处于生产区间
            {
                StoreSum = StoreSum + Produce_speed;
//                qDebug()<<"[Producer]StoreSum = "<<StoreSum;
            }
            else
            {
                emit stopProduce();//库存数量满了就发送信号
            }
        }
        Mutex.unlock();
        sleep(1);//注意这个延时的位置,不可以放在锁里面
    }
}
//绑定本类里面的stopProduce信号,自己控制自己的生产停止或者开始
void Producer::stopProduce_slot()
{
    isProduce = 0;
}

5.主线程

主线程首先需要

  1. 完成UI的一些显示和控制,通过UI的信号和槽函数去改变一些全局变量比如调节生产的速度并且显示,添加或者减少消费者的数量,实时显示商品数量库存的状态
  2. 在构造函数里面创建并且start生产者线程
  3. 在构造函数里面创建消费者管理类,并且根据UI的控件操作去调用消费者管理类里的创建和删除消费者线程。

mainwindow.ui
在这里插入图片描述
mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "custom.h"
#include "producer.h"
#include "appdata.h"
#include "custommanager.h"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    //定时器:可以去看看qt的两种定时器的使用
    void timerEvent(QTimerEvent *event);


private:
    Ui::MainWindow *ui;
    //接收定时器的返回值,同时也是timerEvent()里面的判断依据
    int ShowInfoTime;
   //生产者线程和消费者管理类的声明
    Producer *producer;
    CustomManager *customManager;

signals:
    void StartProduce();
    void storeSum_lack();

public slots:
    void updateInfo();
    void stopProduceSlot();
    void startProduceSlot();

	//UI控件的槽函数
    void Test_btn_clicked_slot();
    void speedUp_btn_clicked_slot();
    void speedDown_btn_clicked_slot();
    void addCustom_btn_clicked_slot();
    void reduceCustom_btn_clicked_slot();
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    updateInfo();

    producer = new Producer;
    customManager = new CustomManager();
    ShowInfoTime = startTimer(500);
    producer->start();

    //类之间的信号与槽
    connect(producer,SIGNAL(stopProduce()),this,SLOT(stopProduceSlot()));
    //不知道为啥无法接收到消费者的信号了,所以改成主线程自动判断
    connect(this,SIGNAL(storeSum_lack()),this,SLOT(startProduceSlot()));

    //ui的信号与槽
    connect(ui->Test_btn,SIGNAL(clicked()),this,SLOT(Test_btn_clicked_slot()));
    connect(ui->speedUp_btn,SIGNAL(clicked()),this,SLOT(speedUp_btn_clicked_slot()));
    connect(ui->speedDown_btn,SIGNAL(clicked()),this,SLOT(speedDown_btn_clicked_slot()));
    connect(ui->addCustom_btn,SIGNAL(clicked()),this,SLOT(addCustom_btn_clicked_slot()));
    connect(ui->reduceCustom_btm,SIGNAL(clicked()),this,SLOT(reduceCustom_btn_clicked_slot()));
}
MainWindow::~MainWindow()
{
    delete ui;
}
//定时器事件
void MainWindow::timerEvent(QTimerEvent *event)
{
    if(event->timerId() == ShowInfoTime)
    {
        updateInfo();
    }
}
//更新显示全局变量里面的信息
void MainWindow::updateInfo()
{
    ui->produce_speed_lb->setText(QString::number(Produce_speed));  //显示生产速度
    ui->store_sum_lb->setText(QString::number(StoreSum));           //显示库存
    if(StoreSum<=10)
    {
        emit storeSum_lack();
    }
    ui->custom_sum_lb->setText(QString::number(Custom_sum));
}

//显示库存的状态信息
void MainWindow::stopProduceSlot()
{
    ui->InfoShow_testBrowser->append("[主线程]库存满,停止生产...");
}

void MainWindow::startProduceSlot()
{
    if(!producer->isProduce)
    {
        producer->isProduce = 1;
        ui->InfoShow_testBrowser->append("[主线程]库存不足,开始生产...");
    }
    else
    {
        Produce_speed+=2;
        ui->InfoShow_testBrowser->append("[主线程]生产速度太慢,加快生产...");
    }
}

//测试按钮
void MainWindow::Test_btn_clicked_slot()
{
    //在写的时候可以把一些操作先在测试按钮里面写个大概
}

//生产提速
void MainWindow::speedUp_btn_clicked_slot()
{
    if(Produce_speed<Produce_speedMax)
    {
        Produce_speed++;
        ui->produce_speed_lb->setText(QString::number(Produce_speed));
    }
}

//生产降速
void MainWindow::speedDown_btn_clicked_slot()
{
    if(Produce_speed>Produce_speedMin)
    {
        Produce_speed--;
        ui->produce_speed_lb->setText(QString::number(Produce_speed));
    }
}

//通过消费者管理类增加一个消费者
void MainWindow::addCustom_btn_clicked_slot()
{
    customManager->creatCustom();
    ui->custom_sum_lb->setText(QString::number(Custom_sum));
}

//通过消费者管理类减少一个消费者
void MainWindow::reduceCustom_btn_clicked_slot()
{
    customManager->delCustom();
    ui->custom_sum_lb->setText(QString::number(Custom_sum));
}

main.cpp

#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

总结

  1. 完成这个demo后其实对很多地方还是一知半解,比如线程的退出是否严谨,线程的同步还可以使用QReadWriterLock、QWaitCondition或者是QSemaphore会更好一些,不确定里面的一些基类的注释是不是有误。
  2. 完整的代码文件在另一个帖子。有问题或者对代码里面的建议请联系我,谢谢。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

QT/C++多线程练习:单生产者多消费者(巨详细版本) 的相关文章

  • 数据结构与算法(Java版) | 本套系列教程的课程亮点和授课方式

    接下来 xff0c 在这一讲 xff0c 我会花一点时间同同学们达成一个共识 xff0c 就是我们这套系列教程在讲述的时候 xff0c 究竟是以一种什么方式来讲述的 我希望 xff0c 经过我的讲解之后 xff0c 大家能够对我们这套系列教
  • 数据结构与算法(Java版) | 数据结构与算法的关系

    从这一节起 xff0c 咱们就要开始进入到 第二章 数据结构与算法的介绍 的学习中了 xff0c 总的来说 xff0c 第二章要讲解的内容其实也不是特别的多 xff0c 内容也多偏理论 xff0c 相信大家学起来是会比较轻松愉快的 接下来
  • 数据结构与算法(Java版) | 就让我们来看看几个实际编程中遇到的问题吧!

    上一讲 xff0c 我给大家简单介绍了一下数据结构 xff0c 以及数据结构与算法之间的关系 xff0c 照理来说 xff0c 接下来我就应该要给大家详细介绍线性结构和非线性结构了 xff0c 但是在此之前 xff0c 我决定还是先带着大家
  • 数据结构与算法(Java版) | 线性结构和非线性结构

    之前 xff0c 我们说过 xff0c 数据结构是算法的基础 xff0c 因此接下来在这一讲我就要来给大家重点介绍一下数据结构了 首先 xff0c 大家需要知道的是 xff0c 数据结构包括两部分 xff0c 即线性结构和非线性结构 知道这
  • 数据结构与算法(Java版) | 稀疏数组的应用场景

    接下来 xff0c 我们就要正式进入第三章 稀疏数组和队列的学习中了 xff0c 顾名思义 xff0c 在这一章节我会为大家介绍两种数据结构 xff0c 即稀疏数组和队列 当然 xff0c 按照我们这套系列教程的安排 xff0c 首先我会为
  • Lottie源码浅探

    Lottie xff08 源码版本 xff1a 2 5 4 xff09 动画步骤 xff1a 前置知识 xff1a Lottie对动画的变换主要是通过Matrix实现 xff0c 因此需要了解Matrix相关知识 xff0c 可以参考下面的
  • 数据结构与算法(Java版) | 稀疏数组的一个实际应用案例

    提出需求 xff1a 使用稀疏数组来保存类似棋盘或者地图等等映射而来的二维数组 xff0c 然后存盘 xff0c 并且希望可以重新恢复为原来的二维数组 通过上一讲的学习 xff0c 我们知道了使用稀疏数组即可保存类似下面的二维数组 xff0
  • 如何进行日常写作训练?

    我似乎陷入了一种巨大的痛苦漩涡中而不能自拔 xff01 最近我感觉写一篇文章真的是太费劲了 xff0c 就好像要用尽我全身力气似的 xff0c 这种感觉实在是太窒息了 xff0c 第一次体验到写一篇文章写得都快要死了的那种感受 xff0c
  • 向刘邦同志学习

    引言 最近在知乎闲逛 xff0c 无意中看到一个问题 xff0c 即刘邦最大的才能是什么 xff0c 顿时觉得这个问题很有趣 xff0c 于是就留心多翻了一些回答 xff0c 发现其中有一个叫张福来的用户的回答深得我心 xff0c 遂果断记
  • 数据结构与算法(Java版) | 队列的应用场景和介绍

    队列的一个应用场景 给大家介绍完稀疏数组这种数据结构之后 xff0c 接下来我再来给大家介绍另外一种数据结构 xff0c 即队列 队列 xff0c 听其名而知其义 xff0c 相信大家应该都在现实生活中见过 xff0c 比如在火车站排队买票
  • 数据结构与算法(Java版) | 数组模拟队列的思路分析与代码实现

    思路分析 上一讲我们讲过 xff0c 队列既可以用数组来实现 xff0c 也可以用链表来实现 xff0c 但由于我们比较熟悉数组这种结构 xff0c 所以这里我会先给大家讲一下数组这种实现方式 xff0c 至于链表这种实现方式 xff0c
  • android 8.1 MTK 方案修改记录

    Music播放音乐时锁屏不需要显示专辑封面 span class token operator 43 43 span span class token operator 43 span b span class token operator
  • centos 5 yum 不能用出现all mirror URLs are not use ftp http or file

    新装了一个centos 5 11 xff0c yum一直没法用 xff0c 每次使用都是出现下图的提示 xff1a 在网上找各种解决方案 xff0c 都是网络没连上 xff0c DNS有问题之类的 直到找到一篇帖子提到centos 5所有资
  • 远程连接——SSH

    简介 SSH xff08 Secure Shell xff09 是一种安全通道协议 xff0c 主要用来实现字符界面的远程登录 远程 复制等功能 SSH 协议对通信双方的数据传输进行了加密处理 xff0c 其中包括用户登录时输入的用户口令
  • Ubuntu 安装LLVM

    在部署galois时 xff0c 需要安装libllvm gt 61 7 0 with RTTI support xff0c 但是如果使用直接编译好的 xff0c 则并不会对RTTI提供支持 因此选择下载源代码自己编译安装 xff0c 并设
  • 关于ubuntu分区挂载

    转载自https blog csdn net u010409517 article details 88081911 一 硬盘分区 1 查看硬盘及所属分区情况 sudo fdisk lu 如图显示 xff0c 我们对200G硬盘进行分区 x
  • Ubuntu开启SSH远程登录

    本文介绍如何在Ubuntu下开启ssh服务并能通过Xshell进行远程登录的方法 测试使用的是在虚拟机上装的Ubuntu和window10 首先更新自己Ubuntu的源 xff0c 具体自行解决 更改IP地址 可以在设置里面设置 xff0c
  • SSH配置免密登录方法

    转载自https blog csdn net jeikerxiao article details 84105529 1 客户端生成公私钥 本地客户端生成公私钥 xff1a xff08 一路回车默认即可 xff09 ssh span cla
  • 阿里云上为服务器申请外网网卡并绑定公网ip

    在阿里云上创建ECS实例的时候 xff0c 云会自动配置公网ip和内网ip 但实质在该ECS实例上只有内网ip xff0c 可以通过ifconfig进行查看 xff0c 如下图 xff1a eth0是一个内网网卡 xff0c 上面绑定的是内
  • C++中常用函数 (持续更新ing...)

    access 函数std memsetfseek函数ftell std condition variable notify one notify allfread

随机推荐