提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
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牛逼