【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牛逼

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 下面是一个使用 Qt 线程池实现生产者消费者模型的简示例: ```cpp #include <QtCore> #include <iostream> class Producer : public QObject { Q_OBJECT public: Producer(QThreadPool *pool, QMutex *mutex, QWaitCondition *cond, QList<int> *data) : m_pool(pool), m_mutex(mutex), m_cond(cond), m_data(data) { m_count = 0; } public slots: void produce() { int num = qrand() % 100; QRunnable *task = new Task(num, m_mutex, m_cond, m_data); m_pool->start(task); m_count++; std::cout << "Produced " << num << std::endl; m_cond->wakeAll(); } int count() const { return m_count; } private: QThreadPool *m_pool; QMutex *m_mutex; QWaitCondition *m_cond; QList<int> *m_data; int m_count; }; class Consumer : public QObject { Q_OBJECT public: Consumer(QMutex *mutex, QWaitCondition *cond, QList<int> *data) : m_mutex(mutex), m_cond(cond), m_data(data) { } public slots: void consume() { m_mutex->lock(); while (m_data->isEmpty()) { m_cond->wait(m_mutex); } int num = m_data->takeFirst(); std::cout << "Consumed " << num << std::endl; m_mutex->unlock(); } private: QMutex *m_mutex; QWaitCondition *m_cond; QList<int> *m_data; }; class Task : public QRunnable { public: Task(int num, QMutex *mutex, QWaitCondition *cond, QList<int> *data) : m_num(num), m_mutex(mutex), m_cond(cond), m_data(data) { } void run() { m_mutex->lock(); while (m_data->size() >= 10) { m_cond->wait(m_mutex); } m_data->append(m_num); m_mutex->unlock(); } private: int m_num; QMutex *m_mutex; QWaitCondition *m_cond; QList<int> *m_data; }; int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); qsrand(QTime::currentTime().msec()); QThreadPool pool; pool.setMaxThreadCount(5); QMutex mutex; QWaitCondition cond; QList<int> data; Producer producer(&pool, &mutex, &cond, &data); Consumer consumer(&mutex, &cond, &data); QObject::connect(&producer, &Producer::produce, &consumer, &Consumer::consume); QTimer timer; QObject::connect(&timer, &QTimer::timeout, &producer, &Producer::produce); timer.start(1000); QTimer countTimer; QObject::connect(&countTimer, &QTimer::timeout, [&producer]() { std::cout << "Produced " << producer.count() << " items" << std::endl; }); countTimer.start(5000); return app.exec(); } ``` 在上面的示例中,`Producer` 和 `Consumer` 类继承自 `QObject`,并使用 Qt 信号和槽机制进行通信。`Producer` 类的 `produce()` ### 回答2: Qt线程池可以很好地实现生产者消费者模型生产者消费者模型主要是通过一个共享的缓冲区来实现生产者生产数据,消费者消费数据的过程。下面是一个使用Qt线程池实现生产者消费者模型的示例: 1. 定义一个数据结构,用于存储生产者消费者共享的数据。这个数据结构可以是一个队列或者一个列表。 2. 创建一个Qt线程池,并设置线程池的最大线程数。 3. 创建生产者线程和消费者线程,并将它们添加到线程池中。 4. 在生产者线程中,生产数据并将数据添加到共享的数据结构中。可以使用Qt的信号槽机制,在生产者线程中发送信号,将数据发送给消费者线程。 5. 在消费者线程中,从共享的数据结构中获取数据并进行消费。可以使用Qt的信号槽机制,在消费者线程中接收信号,并处理收到的数据。 6. 在主线程中,等待生产者线程和消费者线程完成工作,并关闭线程池。 通过使用Qt线程池,可以方便地管理多个线程,避免手动管理线程的创建和销毁,从而简化了生产者消费者模型实现。此外,Qt的信号槽机制可以方便地实现线程间的通信,从而实现生产者消费者之间的数据传递。 ### 回答3: Qt是一个跨平台的C++应用程序开发框架,它提供了丰富的类库和工具,其中也包括线程池的实现。在Qt中,可以使用QThreadPool类来创建和管理线程池。 生产者消费者模型是一种常见的多线程编程模型,其中有一组线程作为生产者,负责生成数据,另一组线程作为消费者,负责处理这些数据。线程池可以很好地支持这种模型,并提供了以下步骤来实现: 1. 创建一个线程池对象:使用QThreadPool类创建一个线程池对象,设置最大线程数、线程闲置时间等属性。 2. 创建生产者线程:通过继承QRunnable类,实现自己的生产者线程类。在类中重写run()函数,在其中完成需要生产的数据的生成。 3. 创建消费者线程:同样通过继承QRunnable类,实现自己的消费者线程类。在类中重写run()函数,在其中完成对生产者生成的数据的处理。 4. 将任务添加到线程池:使用QThreadPool的start()函数将生产者消费者线程对象添加到线程池中,线程池会自动分配线程去运行这些任务。 5. 等待线程池完成任务:可以使用QThreadPool的waitForDone()函数来等待线程池中的所有任务完成,确保所有生产者消费者线程都执行完毕。 通过以上步骤,我们可以在Qt实现生产者消费者模型。线程池可以很好地管理线程的创建和销毁,提高线程利用率和系统的性能。同时,Qt的线程池也提供了一些其他的功能,比如任务优先级和取消线程等,可以根据实际需求来灵活调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值