【qt实现生产者消费者】

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

提示:这里可以添加本文要记录的大概内容:

操作系统是四大件之一,这里为了完成对应的生产者消费者实验,用qt进行了实现。本文就介绍一下怎么用qt的多线程来实现生产者消费者。(本文主要注重代码的实现,原理部分如信号量就不详细讲啦)


一、开发平台

win10 , qt5

二、原理介绍

这里讨论最简单的,一个生产者和一个消费者对一片缓冲区进行读写操作。当缓冲区满时,生产者不能再进行生产,当缓冲区为空时,消费者不能再进行消费。
接下来上一段的代码,介绍一下p,v操作的实现

void p(int &s)
{
    while(s<=0){
        //qDebug("mutex:%d ,empty:%d ,full:%d\n",mutex,empty,full);
        sleep(1);//睡眠一秒
    }
    s--;
}

void v(int &s)
{
    s++;
}

这里两个原语操作,s可以为对应的缓冲资源,以及互斥变量mutex
full,empty代表缓冲区已经有资源的的数量和空缓冲区的数量。

三、代码实现

1.总体的设计思路

(1)ui界面

两个按钮,一个进度条,以及若干label来标注信息,其中一个按钮是启动/停止生产者线程,另外一个是启动/停止消费者线程,进度条来表示缓冲区已经被使用的空间

(2)类的大概设计

一个生产者类Producer,一个消费者类Consumer,二者继承QThread,实现run()函数,由于Qt中不能在子线程操作ui,(否则ui界面会卡死),所以必须在主线程建立一个槽函数,接收两个子线程发过来的信号,以操控ui
然后就是有一个widget类啦,它里面的成员有一个Producer的实例化对象和一个Consumer类的实例化对象 (主要用来建立对应槽函数连接) 再然后就是三个槽啦,用来接收对应的信号(两个按钮的点击,以及一个接收生产者消费者发送的信号的槽函数)、、、

具体的见代码

2.ui界面的设计

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

3.类的设计

(1).生产者Producer

class Producer :public QThread
{
    Q_OBJECT
public:
    bool isRun = false;
    void run();
    void changeIsRun();
    void p(int &s);
    void v(int &s);
    Producer();
    ~Producer();
signals:
    void sendSignal();
};

其中需要继承QThread,另外加上一个Q_OBJECT的宏,如果不加这个宏,是用不了信号槽的,另外准备一个信号sendSignal()用来发信号。

主要函数的实现:

void Producer::run()
{
    qDebug("mutex:%d ,empty:%d ,full:%d\n",mutex,empty,full);
    while(isRun){
        Sleep(100);//生产一个消费品
        p(empty);
        p(mutex);
        qDebug()<<"produceing.....";
        que.enqueue(1);
        emit sendSignal();
        qDebug()<<"pro 信号发射..."<<endl;
        v(mutex);
        v(full);
    }
}

先对资源信号 empty p操作,如果缓冲区一个空位置都没有,就一直死循环,然后对互斥变量mutex进行p操作以保证同一时刻只能有一个线程对缓冲区资源操作。切记不能先对互斥变量mutex进行p操作,这样会引起死锁。 que是一个全局变量 QQueue的对象,向其中添加一个元素,就代表缓冲区被使用一个啦。每向缓冲区添加一个元素时,ui界面的进度条就应该发生变化,这里就发送了一个信号sendSignal(),widget类中接收到了信号就根据队列的大小que.size()来更新进度条的值,最后进行v操作,消耗了一个empty自然就多了一个full,使用完啦一个互斥变量,结束之后,就又可以又一个进程来使用信号量资源啦,所以要对mutex和full进行v操作

void Producer::changeIsRun()
{
    isRun = !isRun;
}

isRun对应按钮的开始与关闭,用来控制线程的结束

void Producer::p(int &s)
{
    while(s<=0){
        qDebug("mutex:%d ,empty:%d ,full:%d\n",mutex,empty,full);
        sleep(1);
    }
    s--;
}

void Producer::v(int &s)
{
    s++;
}

简单的p , v 操作就不用说啦。

(2)消费者

class Consumer :public QThread
{
    Q_OBJECT
public:
    bool isRun = false;
    void run();
    void changeIsRun();
    void p(int &s);
    void v(int &s);
    Consumer();
    ~Consumer();
signals:
    void sendSignal();
};

大概的函数实现与生产者没有差别,唯一不同的是run()函数的实现


void Consumer::run()
{
    while(isRun){
        Sleep(100);//消费一个消费品
//        p(mutex);
//        if(full<=0){//不加的话会死锁
//            mutex++;continue;
//        }
        p(full);
        p(mutex);
        qDebug()<<"consume.....";
        if(que.size()>0)
            que.dequeue();
        emit sendSignal();
        qDebug()<<"con 信号发射..."<<endl;
//        v(empty);
        v(mutex);
        v(empty);
    }
}

pv操作和生产者一样,不过对应的信号量不同。同样的,在消费缓冲区的数据时,需要发送信号。这里为了避免存在对空队列进行弹出操作,加了了一个if。

(3)widget类

class Widget : public QWidget
{
    Q_OBJECT

public:
    Producer p;
    Consumer c;
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();

public slots:
    void proBtnClicked();
    void conBtnClicked();
    void getSignal();
private:
    Ui::Widget *ui;
};

要说的也没有什么啦,已经在 总体的设计思路 那里说啦。直接看对应的几个重要函数实现吧。

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    connect(ui->proBtn,SIGNAL(clicked()),this,SLOT(proBtnClicked()));
    connect(ui->conBtn,SIGNAL(clicked()),this,SLOT(conBtnClicked()));
    connect(&p,&Producer::sendSignal,this,&Widget::getSignal);
    connect(&c,&Consumer::sendSignal,this,&Widget::getSignal);

}

前面两个connect,实现对应的按钮点击事件的响应,后面两个connect,一旦消费者生产者两个线程对缓冲区进行操作之后,就接收发过来的信号,并在getSignal函数中,对进度条的进行更新

void Widget::getSignal()
{
    qDebug()<<"接收到信号\n";
    ui->progressBar->setValue(que.size());
}

对进度条的进行更新

void Widget::proBtnClicked()
{
    QString text = ui->proBtn->text();
    if(text == tr("开始生产")){
        ui->proBtn->setText(tr("停止生产"));
        p.changeIsRun();
        p.start();
    }
    else{
        p.changeIsRun();
        ui->proBtn->setText(tr("开始生产"));

    }
}

在点击生产者对应的按钮后,进行对应的处理,在前面Producer类的数据成员中,有一个isRun的bool变量,初始值为false,一旦点击 开始生产 ,调用changeIsRun函数来对其取反,然后再start。
同样的结束的时候也要先调用changeIsRun函数来其取反,可以跳出run()函数的while循环。

void Widget::conBtnClicked()
{
    QString text = ui->conBtn->text();
    if(text == tr("开始消费")){
        ui->conBtn->setText(tr("停止消费"));
        c.changeIsRun();
        c.start();
    }
    else{
        c.changeIsRun();
        ui->conBtn->setText(tr("开始消费"));

    }
}

消费的这个按钮处理函数,和上面生产者那个处理函数没有什么逻辑区别。


四、运行效果

在这里插入图片描述

五、All代码

widget.h

#ifndef WIDGET_H
#define WIDGET_H
#include <QObject>
#include <QWidget>
#include <QThread>
#include <QQueue>
#include <QDebug>
#include "synchapi.h"
namespace Ui {
class Widget;
}

class Producer :public QThread
{
    Q_OBJECT
public:
    bool isRun = false;
    void run();
    void changeIsRun();
    void p(int &s);
    void v(int &s);
    Producer();
    ~Producer();
signals:
    void sendSignal();
};

class Consumer :public QThread
{
    Q_OBJECT
public:
    bool isRun = false;
    void run();
    void changeIsRun();
    void p(int &s);
    void v(int &s);
    Consumer();
    ~Consumer();
signals:
    void sendSignal();
};


class Widget : public QWidget
{
    Q_OBJECT

public:
    Producer p;
    Consumer c;
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();

public slots:
    void proBtnClicked();
    void conBtnClicked();
    void getSignal();
private:
    Ui::Widget *ui;
};

#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"

QQueue<int> que;
int mutex=1;
int full=0;
int empty=100;


Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    connect(ui->proBtn,SIGNAL(clicked()),this,SLOT(proBtnClicked()));
    connect(ui->conBtn,SIGNAL(clicked()),this,SLOT(conBtnClicked()));
    connect(&p,&Producer::sendSignal,this,&Widget::getSignal);
    connect(&c,&Consumer::sendSignal,this,&Widget::getSignal);

}

Widget::~Widget()
{
    delete ui;
}

void Widget::proBtnClicked()
{
    QString text = ui->proBtn->text();
    if(text == tr("开始生产")){
        ui->proBtn->setText(tr("停止生产"));
        p.changeIsRun();
        p.start();
    }
    else{
        p.changeIsRun();
        ui->proBtn->setText(tr("开始生产"));

    }
}

void Widget::conBtnClicked()
{
    QString text = ui->conBtn->text();
    if(text == tr("开始消费")){
        ui->conBtn->setText(tr("停止消费"));
        c.changeIsRun();
        c.start();
    }
    else{
        c.changeIsRun();
        ui->conBtn->setText(tr("开始消费"));

    }
}

void Widget::getSignal()
{
    qDebug()<<"接收到信号\n";
    ui->progressBar->setValue(que.size());
}

void Producer::run()
{
    qDebug("mutex:%d ,empty:%d ,full:%d\n",mutex,empty,full);
    while(isRun){
        Sleep(100);//生产一个消费品
//        if(empty<=0){//不加的话会死锁
//            mutex++;continue;
//        }
        p(empty);
        p(mutex);
        qDebug()<<"produceing.....";
        que.enqueue(1);
        emit sendSignal();
        qDebug()<<"pro 信号发射..."<<endl;
        v(mutex);
        v(full);
    }
}

void Producer::changeIsRun()
{
    isRun = !isRun;
}

void Producer::p(int &s)
{
    while(s<=0){
        qDebug("mutex:%d ,empty:%d ,full:%d\n",mutex,empty,full);
        sleep(1);
    }
    s--;
}

void Producer::v(int &s)
{
    s++;
}

Producer::Producer()
{

}

Producer::~Producer()
{

}


void Consumer::run()
{
    while(isRun){
        Sleep(100);//消费一个消费品
//        p(mutex);
//        if(full<=0){//不加的话会死锁
//            mutex++;continue;
//        }
        p(full);
        p(mutex);
        qDebug()<<"consume.....";
        if(que.size()>0)
            que.dequeue();
        emit sendSignal();
        qDebug()<<"con 信号发射..."<<endl;
//        v(empty);
        v(mutex);
        v(empty);
    }
}

void Consumer::changeIsRun()
{
    isRun = !isRun;
}

void Consumer::p(int &s)
{
    while(s<=0){
        sleep(1);
    }
    s--;
}

void Consumer::v(int &s)
{
    s++;
}

Consumer::Consumer()
{

}

Consumer::~Consumer()
{

}

main.cpp

#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();

    return a.exec();
}

六、一些bug的记录

做这个实验,我一共做了四版,
在这里插入图片描述
中间被QThread多线程搞吐啦。如果稍微观察仔细一点,就可以发现我的代码非常冗余,pv操作,发信号,这些都是重复,本来应该用一个类来封装这些东西,再然后Producer,Consumer类再来继承这个父类。,但是我的实现出来以后,就会出现信号接收不到,ui界面卡死等等离谱bug。最后是在网上找了一份多线程操作ui的代码,才发现我继承两次出现的bug。(至于这个bug为什么会出现,就以后来说吧,现在我只想肝完这篇博客吃饭啦)

总结

qt牛逼

Qt中,生产者消费者模型(Producer-Consumer Problem)是一个经典的并发处理示例,通过信号槽机制实现了线程之间的通信。下面是一个简的例子,展示了如何使用`QQueue`作为缓冲区,以及`QThread`创建生产者消费者线程: 1. 创建生产者线程(Producer Thread): - 在这个线程里,生产者不断生成数据并放入队列中。 ```cpp void producer(Queue *queue) { for (; ; ) { QString data = generateData(); // 模拟数据生成 QApplication::postEvent(queue, new QueueItem(data)); // 发送数据到队列 QThread::sleep(100); // 控制生产频率 } } ``` 2. 创建消费者线程(Consumer Thread): - 消费者从队列中取出数据并处理。 ```cpp void consumer(Queue *queue) { while (true) { QueueItem item; if (queue->isEmpty()) { QApplication::processEvents(); // 检查队列是否为空,如果没有,阻塞等待 } else { item = queue->take(); // 取出队首元素 processData(item.data()); // 处理数据 } } } ``` 3. 使用`QQueue`作为共享数据结构: ```cpp class QueueItem : public QObject { Q_OBJECT QString data; public: QueueItem(const QString &data): data(data) {} QString getData() const { return data; } }; class Queue { QQueue<QueueItem*> items; QMutex mutex; }; ``` 4. 初始化和启动线程: ```cpp Queue queue; QThread producerThread; Producer producerThreadObject(&queue); connect(&producerThreadObject, &Producer::producedData, &queue, &Queue::enqueue); producerThread.start(&producerThreadObject); QThread consumerThread; Consumer consumerThreadObject(&queue); connect(&queue, &Queue::dequeue, &consumerThreadObject, &Consumer::receivedData); consumerThread.start(&consumerThreadObject); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值