通过生产者消费者模型了解Qt的多线程和自定义信号的使用
效果如下图:
生产者消费者模型分析:
- 界面:操作和接收信号
连接生产者和消费者 - 生产者(ThreadOne类):生产商品
开辟新线程生产商品并存入仓库 - 消费者(ThreadTwo类):消费商品
从仓库取出商品 - 仓库(WareHouse类):管理商品并与界面进行交互
负责管理商品的存和取,通过锁的机制构成安全队列
QQueue< QString > m_cargo(仓库队列)
具体代码如下:
1. 界面
{
ui = new Ui::thirdQtClass();
ui->setupUi(this);
this->setFixedSize(1300,610);
this->setWindowTitle("产者消费者模型");
this->setWindowIcon(QIcon("H:/Document/VS/thirdQt/shop.ico"));
ui->TE_producer->setReadOnly(true);
ui->TE_consumer->setReadOnly(true);
ui->CB_producer->setMaxVisibleItems(20);
ui->CB_consumer->setMaxVisibleItems(30);
wh = new WareHouse();
//生产者
connect(ui->PB_producerAdd,SIGNAL(clicked()),this,SLOT(on_producerAdd_click()));
connect(ui->PB_producerChange, SIGNAL(clicked()), this, SLOT(on_producerChange_click()));
connect(ui->PB_producerMinus, SIGNAL(clicked()), this, SLOT(on_producerMinus_click()));
connect(ui->PB_producerOpen, SIGNAL(clicked()), this, SLOT(on_Order_click()));
connect(ui->PB_producerPause, SIGNAL(clicked()), this, SLOT(on_Order_click()));
//消费者
connect(ui->PB_consumerAdd, SIGNAL(clicked()), this, SLOT(on_consumerAdd_click()));
connect(ui->PB_consumerChange, SIGNAL(clicked()), this, SLOT(on_consumerChange_click()));
connect(ui->PB_consumerMinus, SIGNAL(clicked()), this, SLOT(on_consumerMinus_click()));
connect(ui->PB_consumerOpen, SIGNAL(clicked()), this, SLOT(on_Order_click()));
connect(ui->PB_consumerPause, SIGNAL(clicked()), this, SLOT(on_Order_click()));
//日志
connect(wh,SIGNAL(pushOk(int)),this,
SLOT(on_outProducer_pushOk(int)), Qt::BlockingQueuedConnection);//发出信号后 线程阻塞 直至槽函数完成 在接收方线程操作
connect(wh,SIGNAL(takeOk(int)),this,
SLOT(on_outConsumer_takeOk(int)),Qt::QueuedConnection);//发出信号 等待接收信号方函数完成后再调用 在接收方线程操作
}
//生产者
void thirdQt::on_producerAdd_click()
{
int n = ui->CB_producer->count();
QString producer = "";
if (n > 20)
{
QMessageBox::warning(this, "警告", "生产者已至上限!", QMessageBox::Ok, QMessageBox::NoButton);
}
else
{
producer = QString::number(n);
QString str = "生产者" + producer;
ThreadOne * prod = new ThreadOne(wh);
ui->CB_producer->addItem(str,int(prod));
ui->CB_producer->setCurrentIndex(n);
}
}
void thirdQt::on_producerChange_click()
{
int n = ui->LE_producer->text().toInt();
if (n == 0||n>10000)
{
QMessageBox::information(this,"提示","不能为0 且不能大于10000",QMessageBox::Ok,QMessageBox::NoButton);
}
else
{
wh->changeProduction(n);
}
}
void thirdQt::on_producerMinus_click()
{
int n = ui->CB_producer->count();
if (n != 0)
{
n--;
ThreadOne *po = (ThreadOne *)(ui->CB_producer->itemData(n).toInt());
bool stop = true;
po->deleteProducer(stop);
po->terminate();
po->quit();
//po->wait();
delete po;
po = NULL;
ui->CB_producer->removeItem(n);//removeItem 中的数字为下标数
}
else
{
QMessageBox::warning(this,"警告","生产者已为空",QMessageBox::Ok,QMessageBox::NoButton);
}
ui->CB_producer->setCurrentIndex(n-1);
}
void thirdQt::on_outProducer_pushOk(int _size)
{
ui->TE_producer->append("生产完成 当前商品数为:" + QString::number(_size));
ui->L_producer->setText("生产总额:"+ QString::number(wh->countPush()));
}
void thirdQt::on_Order_click()
{
on_producerChange_click();
on_consumerChange_click();
QPushButton *p = static_cast<QPushButton *>(sender());
QString str = p->text();
if (str == "开始生产" || str == "暂停生产")
{
for (size_t i = 0; i < ui->CB_producer->count(); i++)
{
ThreadOne* po = (ThreadOne*)(ui->CB_producer->itemData(i).toInt());
po->startProduction(true);
if (str == "暂停生产")
{
po->startProduction(false);
}
else
{
if (!po->isRunning())
{
po->start();
}
}
}
}
else
{
for (size_t i = 0; i < ui->CB_consumer->count(); i++)
{
ThreadTwo* po = (ThreadTwo*)(ui->CB_consumer->itemData(i).toInt());
po->startConsumption(true);
if (str == "暂停消费")
{
po->startConsumption(false);
}
else
{
if (!po->isRunning())
{
po->start();
}
}
}
}
}
//消费者
void thirdQt::on_consumerAdd_click()
{
int n = ui->CB_consumer->count();
QString consumer = "";
if (n >30)
{
QMessageBox::warning(this, "警告", "消费者已至上限!", QMessageBox::Ok, QMessageBox::NoButton);
}
else
{
consumer = QString::number(n);
QString str = "消费者" + consumer;
ThreadTwo * csm = new ThreadTwo(wh);
ui->CB_consumer->addItem(str,int(csm));
ui->CB_consumer->setCurrentIndex(n);
}
}
void thirdQt::on_consumerChange_click()
{
int n = ui->LE_consumer->text().toInt();
if (n == 0 || n>10000)
{
QMessageBox::information(this, "提示", "不能为0 且不能大于10000", QMessageBox::Ok, QMessageBox::NoButton);
}
else
{
wh->changeConsumption(n);
}
}
void thirdQt::on_consumerMinus_click()
{
int n = ui->CB_consumer->count();
if (n != 0)
{
ThreadTwo * po = (ThreadTwo*)(ui->CB_consumer->itemData(n-1).toInt());
bool stop = true;
po->deldetComsumption(stop);
po->terminate();
po->quit();
delete po;
po = NULL;
ui->CB_consumer->removeItem(n-1);//removeItem 中的数字为下标数
}
else
{
QMessageBox::warning(this, "警告", "消费者已为空", QMessageBox::Ok, QMessageBox::NoButton);
}
ui->CB_consumer->setCurrentIndex(n - 2);
}
void thirdQt::on_outConsumer_takeOk(int _size)
{
if (_size == 0)
{
ui->TE_consumer->append(QString("暂无商品"));
}
else
{
QString str = "消费完成 商品剩余:" + QString::number(_size);
ui->TE_consumer->append(str);
ui->L_consumer->setText( "消费总额:"+QString::number(wh->countTake()));
}
}
一些基本的信号与槽函数的连接和功能的实现
添加生产者
void thirdQt::on_producerAdd_click()
{
int n = ui->CB_producer->count();
QString producer = "";
if (n > 20)
{
QMessageBox::warning(this, "警告", "生产者已至上限!", QMessageBox::Ok, QMessageBox::NoButton);
}
else
{
producer = QString::number(n);
QString str = "生产者" + producer;
ThreadOne * prod = new ThreadOne(wh);
ui->CB_producer->addItem(str,int(prod));
ui->CB_producer->setCurrentIndex(n);
}
}
利用QComboBox类的属性,每添加一个消费者就new出一个生产者类(ThreadOne)的对象,并将地址存入QComboBox的菜单项的QVariant()中。
这里可以了解QComboBox类的一些功能,QComboBox类的addItem()可以传递一个QString,和一个QVariant()。
QString str = "生产者" + producer;
ThreadOne * prod = new ThreadOne(wh);
ui->CB_producer->addItem(str,int(prod));
既当我们添加一个菜单项的同时为这个菜单项绑定一个看不见的数据。这里我们存放ThreadOne对象的指针。
消费者同理
- 开始按钮
生产者和消费者公用一个槽函数。添加生产者或消费者后按下开始后,通过遍历将菜单栏中对应的对象取出,并判断是否为启动状态(isRunning()),若该生产者(消费者)未启动,则start()开启线程。
void thirdQt::on_Order_click()
{
on_producerChange_click();
on_consumerChange_click();
QPushButton *p = static_cast<QPushButton *>(sender());
QString str = p->text();
if (str == "开始生产" || str == "暂停生产")
{
for (size_t i = 0; i < ui->CB_producer->count(); i++)
{
ThreadOne* po = (ThreadOne*)(ui->CB_producer->itemData(i).toInt());
po->startProduction(true);
if (str == "暂停生产")
{
po->startProduction(false);
}
else
{
if (!po->isRunning())
{
po->start();
}
}
}
}
else
{
for (size_t i = 0; i < ui->CB_consumer->count(); i++)
{
ThreadTwo* po = (ThreadTwo*)(ui->CB_consumer->itemData(i).toInt());
po->startConsumption(true);
if (str == "暂停消费")
{
po->startConsumption(false);
}
else
{
if (!po->isRunning())
{
po->start();
}
}
}
}
}
- 删除消费者(生产者)
与添加类似,先拿出菜单中的末尾项所绑定的对象,终止线程 并删除指针,最后删除菜单项
void thirdQt::on_consumerMinus_click()
{
int n = ui->CB_consumer->count();
if (n != 0)
{
ThreadTwo * po = (ThreadTwo*)(ui->CB_consumer->itemData(n-1).toInt());
bool stop = true;
po->deldetComsumption(stop);
po->terminate();
po->quit();
delete po;
po = NULL;
ui->CB_consumer->removeItem(n-1);//removeItem 中的数字为下标数
ui->CB_consumer->setCurrentIndex(n - 2);
}
else
{
QMessageBox::warning(this, "警告", "消费者已为空", QMessageBox::Ok, QMessageBox::NoButton);
}
}
connect(wh,SIGNAL(pushOk(int)),this,
SLOT(on_outProducer_pushOk(int)), Qt::BlockingQueuedConnection);//发出信号后 线程阻塞 直至槽函数完成 在接收方线程操作
connect(wh,SIGNAL(takeOk(int)),this,
SLOT(on_outConsumer_takeOk(int)),Qt::QueuedConnection);//发出信号 等待接收信号方函数完成后再调用 在接收方线程操作
接收来自仓库的带参的自定义信号pushOk()、takeOk()、其中参数分别为存入和取出后仓库商品数量。
connect()的第五参数: Qt::BlockingQueuedConnection
Qt::QueuedConnection
这两种注释有解释
值得注意的是默认状态下若信号与槽函数不在同一线程则默认使用 : Qt::DirectConnection
Qt::DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。但当线程过多时可能导致线程崩溃,所以主动设置第五参数为最优选择。
2. 生产者
wh 是仓库的对象,通过此对象操作仓库的函数。
ThreadOne::ThreadOne(WareHouse *wh)
{
m_wh = wh;
begin = false;
stop = false;
}
void ThreadOne::startProduction(bool order)
{
begin = order;
}
void ThreadOne::run()
{
while (!stop)
{
while (begin)
{
QString commodity = "¥";
m_wh->pushCargo(commodity);
msleep(50);
begin = order();
}
stop = getDelete();
sleep(1);
}
}
bool ThreadOne::order()
{
return begin;
}
void ThreadOne::deleteProducer(bool stop1)
{
stop = stop1;
}
bool ThreadOne::getDelete()
{
return stop;
}
ThreadOne类继承自QThread类,此类负责Qt的多线程
QThread中有一个run()的虚函数,继承并重写后的run()函数将在新的线程中执行,所以想要实现多线程就需要新建一个类继承QThread()并重写run()函数。
void ThreadOne::run()
{
while (!stop)
{
while (begin)
{
QString commodity = "¥";
m_wh->pushCargo(commodity);
msleep(50);
begin = order();
}
stop = getDelete();
sleep(1);
}
}
大体上与生产者相似 run()外的函数用来控制暂停和停止
#include "ThreadTwo.h"
ThreadTwo::ThreadTwo(WareHouse * wh)
{
m_wh = wh;
stop = false;
}
void ThreadTwo::run()
{
while (!stop)
{
while (begin)
{
QString consumer = m_wh->takeCargo();
msleep(80);
begin = order();
}
sleep(1);
stop = getDelete();
}
}
void ThreadTwo::startConsumption(bool order)
{
begin = order;
}
bool ThreadTwo::order()
{
return begin;
}
void ThreadTwo::deldetComsumption(bool stop1)
{
stop = stop1;
}
bool ThreadTwo::getDelete()
{
return stop;
}
自定义两个带参信号:signals:
void pushOk(int);
void takeOk(int);
存取操作都使用了QMutex类(锁),为了防止不同对象访问队列形成冲突,通过加锁的形式保证仓库为一个安全队列。(当对象开始访问队列 触发lock()状态,使其他对象无法进入,当操作完成后 触发unlock()打开队列锁结束访问 )。
每当生产者生产的商品被存入队列,WareHouse 便发送一个带有仓库数量参数的信号(pushOk() )返回给界面。
同理每当消费者拿走一个商品,仓库便删除一个商品,并发送一个带有仓库数量参数的信号(takeOk() )返回给界面。
WareHouse::WareHouse()
{
mutex = new QMutex();
m_push = 0;
m_take = 0;
m_sleepP = 0;
m_sleepC = 0;
}
//存入商品
void WareHouse::pushCargo(QString commodity)
{
mutex->lock();
m_push++;
m_cargo.enqueue(commodity);
emit pushOk(m_cargo.size());
mutex->unlock();
QThread::msleep(getChangeP());
}
//消费商品
QString WareHouse::takeCargo()
{
QString str = "";
mutex->lock();
if (m_cargo.count() != 0)
{
str = m_cargo.dequeue();
emit takeOk(m_cargo.size());
m_take++;
}
else
{
emit takeOk(0);
}
mutex->unlock();
QThread::msleep(getChangeC());
return str;
}
int WareHouse::countPush()
{
int pushSize = m_push;
return pushSize;
}
int WareHouse::countTake()
{
int takeSize = m_take;
return takeSize;
}
void WareHouse::changeProduction(int datas)
{
m_sleepP = 10000 / datas;
}
int WareHouse::getChangeP()
{
int n = m_sleepP;
return n;
}
void WareHouse::changeConsumption(int datas)
{
m_sleepC = 10000 / datas;
}
int WareHouse::getChangeC()
{
int n = m_sleepC;
return n;
}
以上便是本项目的主要代码,有问题或建议请在评论区交流,如需源码请转至:资源下载中心