背景:
个人学习多线程控制,写了一些博文用于记录:
【Qt线程-1】this,volatile,exec(),moveToThread()
【Qt线程-2】事件循环(QCoreApplication::processEvents,exec)的应用
【Qt线程-3】使用事件循环,信号,stop变量,sleep阻塞,QWaitCondition+QMutex条件变量,退出子线程工作
【Qt线程-4】事件循环嵌套,BlockingQueuedConnection与QWaitCondition比较
【Qt线程-5】生产者&消费者模型应用(多态,子线程控制,协同,事件循环)
【Qt线程-6】获取当前线程id,thread()和currentThreadId(),不是想当然那样,不使用信号槽可能看不出区别
【Qt线程-7】QThread、QThreadPool+QRunnable怎么选
本篇所谓生产者与消费者,是靠个人理解的整理,未必准确。感兴趣的可以互相交流指正。就如我对“观察者”的理解,就跟网上讲得不一样。
由于是个人笔记,文中大量代码和注释,难免有措辞不当,理解万岁。
即使以学习为目的,但若是应用到实际大型项目中,还会有更多问题。我的习惯是,善于写demo多动手实验。希望能进步赶上大家的步伐。
场景概述:
主窗体作为主界面和中转仓库。可以直观看到仓库存量和中转计数,可以清空仓库和计数,可以调整仓库容量,可以新建/销毁各单位,可以看到各单位列表。
生产者和消费者,每个单位都是独立的个体,都有各自独立的窗体。可以启动/停止工作,可以设置缓存大小,可以设置生产速度,可以看到缓存流动情况和计数。
中途可以随意停止某单位的工作,可以随意关闭某单位的窗体并停止其工作,可以随时关闭主窗体完全退出程序并停止所有工作,停止工作是必须的,不能造成“停止延时”或者“野循环卡死”,可以任意调整组队状态(实时变化生产者和消费者各单位的数量和速度,以便观察生产效率)。
所谓“生产/消费”,“产品”用一个永不重复的字符串代替,便于观察。
学习目的:
通过直观界面指示,观察生产&消费对生产效率的影响。主要是验证实现这种模型的方法。
通过“提升为”实现界面继承,提高界面复用率。
写一个用于生产的工作流程,适用于生产者和消费者。其中用到虚函数、纯虚函数实现多态。事件循环实现无阻塞控制与生产速度。
当用户意外终止操作时,通过条件变量实现子线程的有效控制,避免野循环和卡顿。
界面设计:
主窗体:
各单位窗体(生产者/消费者):
这是一个基类窗体,各单位可以通过“提升为”来继承它。
代码组成:
主窗体/主控/仓库:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QStandardItemModel>
#include <QTime>
#include <QThread>
#include "QCloseEvent"
#include "frm_producer.h"
#include "frm_consumer.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->spinWarehouseSize->setValue(m_iWarehouseSize);
m_modelWarehouse = new QStandardItemModel;
m_modelP = new QStandardItemModel;
m_modelC = new QStandardItemModel;
ui->listWarehouse->setModel(m_modelWarehouse);
ui->listP ->setModel(m_modelP);
ui->listC ->setModel(m_modelC);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::closeEvent(QCloseEvent *event)
{
f_RemoveAll();
event->accept();
}
/*===============================================================
* Warehouse.
*===============================================================*/
bool MainWindow::f_WarehouseIsFull()
{
return (m_modelWarehouse->rowCount() >= m_iWarehouseSize);
}
bool MainWindow::f_WarehouseIsEmpty()
{
return (m_modelWarehouse->rowCount() == 0);
}
void MainWindow::on_spinWarehouseSize_valueChanged(int arg1)
{
m_iWarehouseSize = arg1;
}
void MainWindow::on_btnWarehouseClear_clicked()
{
m_modelWarehouse->clear();
m_iCount_In = 0;
ui->labInCount->setText("0");
m_iCount_Out = 0;
ui->labOutCount->setText("0");
}
void MainWindow::on_btnLogsClear_clicked()
{
ui->txtLogs->clear();
}
/*===============================================================
* Warehouse: Producer set/Consumer get.
*===============================================================*/
void MainWindow::onSet(QStandardItem *item)
{
if (f_WarehouseIsFull())
{
return;
}
m_modelWarehouse->appendRow(item);
QString s = item->text() + " in.";
ui->txtLogs->append(s);
ui->labInCount->setText(QString::number(++m_iCount_In));
}
QStandardItem* MainWindow::f_Get()
{
if (f_WarehouseIsEmpty())
{
return nullptr;
}
QStandardItem* item = m_modelWarehouse->item(0);
QStandardItem* item_clone = item->clone();
return item_clone;
}
void MainWindow::onGet(QString sTxt)
{
/* Because the mutex locker was used in the productor/consumer class.
* So at the every time of the consumer getting, remove the first one
* is ok. It isn't necessary to find the item by the text string and
* delete the item.
*/
// int i = 0;
// while (i < m_modelWarehouse->rowCount())
// {
// QStandardItem *item = m_modelWarehouse->item(i);
// if (item->text() == sTxt)
// {
// delete item;
// m_modelWarehouse->removeRow(i);
// break;
// }
// ++i;
// }
QStandardItem *item = m_modelWarehouse->item(0);
delete item;
m_modelWarehouse->removeRow(0);
QString s = sTxt + " out.";
ui->txtLogs->append(s);
ui->labOutCount->setText(QString::number(++m_iCount_Out));
}
/*===============================================================
* Producer
*===============================================================*/
void MainWindow::on_btnP_add_clicked()
{
Frm_Producer *frm = new Frm_Producer;
f_OpenFrm(frm, QString("P"), m_modelP);
}
void MainWindow::on_btnP_del_clicked()
{
f_RemoveItem(ui->listP);
}
void MainWindow::onP_Remove(QString sTxt)
{
f_RemoveItem(m_modelP, sTxt);
}
/*===============================================================
* Consumer.
*===============================================================*/
void MainWindow::on_btnC_add_clicked()
{
Frm_Consumer *frm = new Frm_Consumer;
f_OpenFrm(frm, QString("C"), m_modelC);
}
void MainWindow::on_btnC_del_clicked()
{
f_RemoveItem(ui->listC);
}
void MainWindow::onC_Remove(QString sTxt)
{
f_RemoveItem(m_modelC, sTxt);
}
/*===============================================================
* Producer/Consumer: open form.
*===============================================================*/
void MainWindow::f_OpenFrm(Frm_PC *frm, QString sTitle, QStandardItemModel *model)
{
QString sFrmTitle = sTitle + QTime::currentTime().toString("HHmmsszzz");
//Stores the producer/consumer in the list of the main ui as a item.
QStandardItem *item = new QStandardItem(sFrmTitle);
item->setData(QVariant::fromValue(frm), Qt::UserRole + eFrm);
model->appendRow(item);
frm->f_Init(sFrmTitle, this, item->clone());//virtual function, polymorphic.
frm->show();
}
/*===============================================================
* Remove the producer/consumer from the ui list.
*===============================================================*/
void MainWindow::f_RemoveItem(QListView *list)
{
QModelIndex index = list->currentIndex();
if (!index.isValid())
{
return;
}
QStandardItemModel *model = dynamic_cast<QStandardItemModel*>(list->model());
QStandardItem *item = model->itemFromIndex(index);
f_RemoveItem(model, item->text());
}
void MainWindow::f_RemoveItem(QStandardItemModel *model, QString sTxt, bool bRemoveAll)
{
/* Here uses the while loop instead of the for loop. Exactly the for loop is ok
* too. But you must to pay attention the 'iRow++' statement. Because after the
* item was removed from the model, the row count of the model has changed. and
* the 'iRow++' statement will cause err. So a 'iRow--' statement is required
* after the 'removeRow' statement. It cannot be understand simply.
*
* So the 'while' loop statement is better.
*/
int iRow = 0;
while (iRow < model->rowCount())
{
QStandardItem *item = model->item(iRow);
if (item->text() == sTxt || bRemoveAll)
{
QMainWindow *frm = item->data(Qt::UserRole + eFrm).value<QMainWindow *>();
if (nullptr != frm)
{
frm->close();
frm->deleteLater();
}
delete item;
model->removeRow(iRow);//This will cause the rowCount changed.
if (!bRemoveAll)
{
break;
}
}
else
{
++iRow;
}
}
}
void MainWindow::f_RemoveAll()
{
f_RemoveItem(m_modelP, "", true);
f_RemoveItem(m_modelC, "", true);
}
单位(生产者&消费者)基类窗体:
下面的代码中我添加了很多注释。虽然英文不好,但尽量用简单的方式进行了详尽的说明。主要是关于条件变量的应用,和对事件循环的理解。
#include "frm_pc.h"
#include "ui_frm_pc.h"
#include <QThread>
#include <QDebug>
const QString g_sResume = "Resume";
const QString g_sPause = "Pause";
Frm_PC::Frm_PC(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::Frm_PC)
{
ui->setupUi(this);
}
Frm_PC::~Frm_PC()
{
f_Destroy();
delete ui;
}
/*===============================================================
* Set title.
*===============================================================*/
void Frm_PC::f_SetHeader(QString s)
{
this->setWindowTitle(s);
ui->labHeader->setText(s);
}
/*===============================================================
* Init.
*===============================================================*/
void Frm_PC::f_Init(QString sTitle, MainWindow *frmMain, QStandardItem *item)
{
m_sTitle = sTitle;
m_frmMain = frmMain;
m_itemUi = item;
//ui
setAttribute(Qt::WA_DeleteOnClose);
m_modelCache = new QStandardItemModel;
ui->listCache->setModel(m_modelCache);
ui->btnPause->setText(g_sResume);
ui->spinSpeed->setMinimum(1);
ui->spinCacheSize->setMinimum(1);
//thread
m_thd = new QThread;
m_thd->start();
connect(this, SIGNAL(sigInitWorker(QString,MainWindow*,QMutex*)), m_worker, SLOT(onInit(QString,MainWindow*,QMutex*)));
connect(this, SIGNAL(sigRun()), m_worker, SLOT(onRun()));
connect(this, SIGNAL(sigResume()), m_worker, SLOT(onResume()));
connect(this, SIGNAL(sigPause()), m_worker, SLOT(onPause()));
connect(this, SIGNAL(sigStop()), m_worker, SLOT(onStop()));
connect(this, SIGNAL(sigSetInterval(int)), m_worker, SLOT(onSetInterval(int)));
connect(this, SIGNAL(sigSetCacheSize(int)), m_worker, SLOT(onSetCacheSize(int)));
connect(m_worker, SIGNAL(sigToUi_Cache_In(QStandardItem *)), this, SLOT(onCache_In(QStandardItem *)));
connect(m_worker, SIGNAL(sigToUi_Cache_Out(QString)), this, SLOT(onCache_Out(QString)));
m_worker->setParent(NULL);
m_worker->moveToThread(m_thd);
emit sigInitWorker(m_sTitle, m_frmMain, &m_frmMain->m_mutex);
//Enable the working state.
ui->spinSpeed->setValue(1);
ui->spinCacheSize->setValue(1);
emit sigRun();
}
/*===============================================================
* Destroy.
*===============================================================*/
void Frm_PC::f_Destroy()
{
/* Stop the sub thread.
*
* Pay attention!!!
*
* IN THE SUB THREAD:
*
* while (!m_bStop)
* {
* QCoreApplication::processEvents();
* }
* slot -> m_bStop = true;
*
* In the event loop the 'deletelater' will be executed.
*
* Use the variable flag:
*
* m_worker->m_bStop = true;
*
* This way is not safe for exposing the memeber variable.
*
* Only use the signal and slot:
*
* emit signal;
* deletelater;
*
* It is a wrong way. Because the slot function and 'deletelater' will be both
* executed in the event loop in the sub thread. When executing the 'deletelater',
* the sub thread will release all its resource. And the value of the variable
* 'm_bStop' is unexpectable. So the while loop may be not quit in the expecting,
* and it will cause the unexpectable err.
*
* Use the signal and slot and the condition variable:
*
* emit signal;
* condition.wait();
* deletelater;
*
* It is ok. After the signal being sent, the current thread will be blocked to
* wait for the sub thread quiting. Until the sub thread quiting complete, the current
* thread is waked up to continue the 'deletelater' operation.
*/
qDebug() << m_sTitle << QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss zzz") << "wait begin.";
m_worker->f_CallerThread_Wait_Prepare();//Lock the mutex first.
emit sigStop();
m_worker->f_CallerThread_Wait();
qDebug() << m_sTitle << QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss zzz") << "wait end.";
if (nullptr != m_worker)
{
m_worker->deleteLater();//in event loop
m_worker = nullptr;
}
if (nullptr != m_thd && m_thd->isRunning())
{
m_thd->quit();
m_thd->wait();
m_thd->deleteLater();//in event loop
m_thd = nullptr;
}
emit sigToUi_RemoveItem(m_itemUi->text());
}
/*===============================================================
* Update cache to ui.
*===============================================================*/
void Frm_PC::onCache_In(QStandardItem *item)
{
ui->txtLogs->append(item->text() + " in cache.");
m_modelCache->appendRow(item);
ui->labCacheInCount->setText(QString::number(++m_iCount_CacheIn));
}
void Frm_PC::onCache_Out(QString s)
{
ui->txtLogs->append(s + " out of cache.");
for (int iRow = 0; iRow < m_modelCache->rowCount(); iRow++)
{
QStandardItem *item = m_modelCache->item(iRow);
if (item->text() == s)
{
int iRow = item->row();
delete item;
m_modelCache->removeRow(iRow);//This will cause the rowCount changed.
break;
}
}
ui->labCacheOutCount->setText(QString::number(++m_iCount_CacheOut));
}
/*===============================================================
* Slots.
*===============================================================*/
void Frm_PC::on_btnLogs_Clear_clicked()
{
ui->txtLogs->clear();
m_iCount_CacheIn = 0;
ui->labCacheInCount->setText("0");
m_iCount_CacheOut = 0;
ui->labCacheOutCount->setText("0");
}
void Frm_PC::on_spinSpeed_valueChanged(int arg1)
{
emit sigSetInterval(arg1);
}
void Frm_PC::on_spinCacheSize_valueChanged(int arg1)
{
emit sigSetCacheSize(arg1);
}
void Frm_PC::on_btnPause_clicked()
{
if (ui->btnPause->text() == g_sResume)
{
emit sigResume();
ui->btnPause->setText(g_sPause);
}
else
{
emit sigPause();
ui->btnPause->setText(g_sResume);
}
}
上述关于条件变量的应用,还单独为此写了博文记录,主要是时序控制问题。
【Qt线程-3】使用事件循环,信号,stop变量,sleep阻塞,QWaitCondition+QMutex条件变量,退出子线程工作
Qt手册关于信号槽提到过,跨线程connect时,消息队列模式下,emit之后的语句会马上执行,槽函数在子线程回到时间循环时执行。但这不严谨,虽然emit和后面的语句紧挨着,大多数情况下没问题。但涉及到多线程并发,os的调度如果不人为控制,时序无法确定。亦即:有那么一种可能,会在父线程执行完emit后,被os调度暂停去执行子线程的槽函数,那就失去条件变量的意义了。所以要在emit之前加锁。
这里还有个非常重要的事,就是待会儿写各单位窗体代码的时候,构造函数中比如注释掉初始化列表和构建ui的部分,否则会重复。见下文。
生产者窗体(子类):
#include "frm_producer.h"
#include "ui_frm_producer.h"
#include "worker_producer.h"
Frm_Producer::Frm_Producer(QWidget *parent) :
Frm_PC(parent)//,
//ui(new Ui::Frm_Producer)
{
//ui->setupUi(this);
//构造函数中的这些注释很重要!因为继承了基类窗体,各种控件和槽函数已经具备,
//如果这里不注释掉,会产生两次构造以及两次槽绑定。
}
Frm_Producer::~Frm_Producer()
{
//delete ui;
}
/*===============================================================
* Init.
*===============================================================*/
void Frm_Producer::f_Init(QString sTitle, MainWindow *frmMain, QStandardItem *item)
{
Worker_Producer *producter = new Worker_Producer;
m_worker = producter;
Frm_PC::f_Init(sTitle, frmMain, item);
Frm_PC::f_SetHeader("Producer");
connect(producter, SIGNAL(sigToWarehouse(QStandardItem*)),
m_frmMain, SLOT(onSet(QStandardItem*)));
connect(this, SIGNAL(sigToUi_RemoveItem(QString)),
m_frmMain, SLOT(onP_Remove(QString)), Qt::UniqueConnection);
}
消费者窗体(子类):
#include "frm_consumer.h"
#include "ui_frm_consumer.h"
#include "worker_consumer.h"
Frm_Consumer::Frm_Consumer(QWidget *parent) :
Frm_PC(parent)//,
//ui(new Ui::Frm_Consumer)
{
//ui->setupUi(this);
//构造函数中的这些注释很重要!因为继承了基类窗体,各种控件和槽函数已经具备,
//如果这里不注释掉,会产生两次构造以及两次槽绑定。
}
Frm_Consumer::~Frm_Consumer()
{
//delete ui;
}
/*===============================================================
* Init.
*===============================================================*/
void Frm_Consumer::f_Init(QString sTitle, MainWindow *frmMain, QStandardItem *item)
{
Worker_Consumer *consumer = new Worker_Consumer;
m_worker = consumer;
Frm_PC::f_Init(sTitle, frmMain, item);
Frm_PC::f_SetHeader("Consumer");
connect(consumer, SIGNAL(sigToWarehouse_GettingDone(QString)),
m_frmMain, SLOT(onGet(QString)));
connect(this, SIGNAL(sigToUi_RemoveItem(QString)),
m_frmMain, SLOT(onC_Remove(QString)), Qt::UniqueConnection);
}
生产流程的基类:
窗体只是负责界面交互,真正的工作肯定是放在子线程中用对象处理。此处写的只是一个可复用的基类。
其中,生产过程这个函数是纯虚函数,继承时必须重写。但它执行的时机,可以在基类中定义好。
缓存控制是虚函数,根据需要选择是否重写,从而分别实现生产者和消费者。
再有就是特别要注意的事件循环的应用,时刻注意它的作用和时机,才能实现好的效果。
生产者&消费者分别派生的子类中,适当应用的sleep释放cpu资源,个人认为,是多线程必须的。
#include "worker.h"
#include <QDebug>
Worker::~Worker()
{
qDebug() << m_sTitle << QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss zzz") << "~Worker;";
qDebug() << "---------------------";
}
void Worker::onInit(QString sTitle, MainWindow *frmMain, QMutex *mutexWarehouse)
{
m_sTitle = sTitle;
m_frmMain = frmMain;
m_mutexWarehouse = mutexWarehouse;
}
void Worker::onRun()
{
m_bStop = false;
m_bAllowWorking = false;
//QDateTime dtWorkingEnd;//Used for method 1 of the heart interval.
while (!m_bStop)
{
/* Pay attention!!!
*
* The event loop will lead to this object's 'deletlater', and all the variables will
* be unknown value and it will lead to unexpect err. So the flag variable 'm_bStop'
* should be determined in the correct place. Then the while loop will quit in time.
*/
/* About heart interval:
* There are two methods to realize the interval. The notes below contains them.
* The method 1 contains 3 steps, the method 2 contains 1 step.
* Perhaps the method 2 is the better.
*/
//---------------------------------------
//Method 1:
/*
QCoreApplication::processEvents();
//Stopped by user.
if (m_bStop)
{
qDebug() << m_sTitle << QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss zzz") << "break;";
break;
}
//Working begin.
if (m_bAllowWorking)
{
if (!m_bStop && m_iInterval > 0)
{
f_CacheIn();//event-loop in it. break on stopping by 'f_GetStopFlag()'.
}
if (dtWorkingEnd.isNull())
{
dtWorkingEnd = QDateTime::currentDateTime().addSecs(m_iInterval);
//Do the specific job realized in the sub class.
if (!m_bStop && m_iInterval > 0)
{
f_DoWorking();
}
}
if (!m_bStop && m_iInterval > 0)
{
f_CacheOut();//event-loop in it. break on stopping by 'f_GetStopFlag()'.
}
}
if (QDateTime::currentDateTime() >= dtWorkingEnd)
{
dtWorkingEnd = QDateTime();
}
*/
//---------------------------------------
//Method 2(step 1/1):
//Working begin.
if (m_bAllowWorking)
{
/* ABOUT THE CACHE:
*
* The cache operation belongs to the working operation. It will resume or pause by
* the flag variable 'mbAllowWorking'. So if the working operation stopped, the cache
* operation stops too. For example, the producer stops sending goods out of the cache,
* or the consumer stops receiving goods into the cache.
*
* These two functions, 'f_CacheIn()' and 'f_CacheOut', are virtual function, which
* have no any implementation in this class. But they may be overridden and implemented
* in the sub class.
*
* ABOUT WORKING:
*
* The function 'f_DoWorking()' is a pure virtual function in this class. It must to
* be overridden and realized in the sub class. Or it will cause compilation err.
*/
if (!m_bStop && m_iInterval > 0)
{
f_CacheIn();//event-loop in it. break on stopping by function 'f_IsStop()'.
}
if (!m_bStop && m_iInterval > 0)
{
f_DoWorking();//Do the specific job realized in the sub class.
}
if (!m_bStop && m_iInterval > 0)
{
f_CacheOut();//event-loop in it. break on stopping by function 'f_IsStop()'.
}
}
QDateTime dt = QDateTime::currentDateTime().addSecs(m_iInterval);
while (!m_bStop && QDateTime::currentDateTime() < dt)
{
QCoreApplication::processEvents();
}
}
//Wake up the caller thread if it is necessary.
f_CallerThread_WakeUp();
}
void Worker::onResume()
{
m_bAllowWorking = true;
}
void Worker::onPause()
{
m_bAllowWorking = false;
}
void Worker::onStop()
{
qDebug() << m_sTitle << QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss zzz") << "m_bStop = true;";
m_bStop = true;
}
void Worker::onSetInterval(int i)
{
m_iInterval = i;
}
void Worker::onSetCacheSize(int i)
{
m_iCacheSize = i;
}
void Worker::f_CallerThread_Wait_Prepare()
{
m_mutex.lock();
}
void Worker::f_CallerThread_Wait()
{
m_condition.wait(&m_mutex);
m_mutex.unlock();
}
void Worker::f_CallerThread_WakeUp()
{
m_mutex.lock();
m_condition.wakeAll();
m_mutex.unlock();
}
void Worker::f_CacheIn(){}
void Worker::f_CacheOut(){}
生产者子类:
#include "worker_producer.h"
#include <QDebug>
void Worker_Producer::f_DoWorking()
{
/* PRODUCTING STEP.
* ----------------
* If the cache is not full. Product a item and put it into the cache.
*/
bool bIsCacheFull = (m_listCache.size() >= m_iCacheSize);
if (!bIsCacheFull)
{
//Create a new item as a product.
QString sTime = QTime::currentTime().toString();
QString s = f_GetTitle() + " " + sTime;
QStandardItem *item = new QStandardItem(s);
//Put it in the cache.
m_listCache.append(item);
//Tell the ui to display the log.
emit sigToUi_Cache_In(item->clone());
}
}
void Worker_Producer::f_CacheOut()
{
/* SENDING STEP.
* -------------
* If the cache is not empty and the warehouse is not full,
* take the first item out of the cache and send it to the warehouse.
* Repeat it until the cache is empty or the warehouse is full.
*/
while (!f_IsStop() && !m_listCache.isEmpty())
{
//If the warehouse is not full, send a product to it.
QMutexLocker locker(m_mutexWarehouse);
if (!m_frmMain->f_WarehouseIsFull())
{
//Get the first on in the cache.
QStandardItem *item_ToWarehouse = m_listCache.first();
QString sTxt = item_ToWarehouse->text();
//Send to warehouse.
emit sigToWarehouse(item_ToWarehouse->clone());
//Remove the first one from the cache.
delete item_ToWarehouse;
m_listCache.removeFirst();
//Tell the ui to display the log.
emit sigToUi_Cache_Out(sTxt);
}
//If the warehouse is full, jump out from the SENDING STEP.
else
{
break;
}
QCoreApplication::processEvents();
QThread::msleep(10);
}
}
消费者子类:
#include "worker_consumer.h"
#include <QDebug>
void Worker_Consumer::f_DoWorking()
{
/* CONSUMING STEP.
* ---------------
* If the cache is not empty, take the first item and consume it.
*/
if (!m_listCache.isEmpty())
{
//Take the first item in the cache.
QStandardItem *item = m_listCache.first();
//Delete it as a consumable.
QString s = item->text();
m_listCache.removeFirst();
delete item;
//Tell the ui to display the log.
emit sigToUi_Cache_Out(s);
}
}
void Worker_Consumer::f_CacheIn()
{
/* GETTING STEP.
* -------------
* If the cache is not full and the warehouse is not empty,
* get the item from the warehouse and put it into the cache.
* Repeat it until the cache is full or the warehouse is empty.
*/
while (!f_IsStop() && (m_listCache.size() < m_iCacheSize))
{
//Take the item back if the warehouse is not empty.
QMutexLocker locker(m_mutexWarehouse);
if (!m_frmMain->f_WarehouseIsEmpty())
{
//Get the first of the warehouse.
QStandardItem *item_FromWarehouse = m_frmMain->f_Get();
//Tell the warehouse to update ui.
QString sTxt = item_FromWarehouse->text();
emit sigToWarehouse_GettingDone(sTxt);
//Put the item in the cache.
m_listCache.append(item_FromWarehouse);
//Tell the ui to display the log.
emit sigToUi_Cache_In(item_FromWarehouse->clone());
}
//If the warehouse is empty, jump out of the GETTING STEP.
else
{
break;
}
QCoreApplication::processEvents();
QThread::msleep(10);
}
}
运行效果:
启动后直接显示主窗体。
分别新建一个生产者和消费者,让它们自行协同工作。
还可以创建多个单位,比如:
一开始只有生产者,它会工作到满仓,然后停止工作。如果有消费者从仓库取货,或者仓库选择清仓,或者仓库容量增大,它才会继续。
一开始只有消费者,显然没活干,直到出现生产者开始填充仓库,它才能取货开始干活。
当生产者和消费者速度匹配时,仓库和缓存无压力。当不匹配时,可以明显看到缓存和仓库的作用。
当调整缓存和仓库容量时,可以看到对全局工作效率的影响。
当生产者速度是消费者两倍时,一个生产者可以打平两个消费者。反之亦然。当然还可以尝试随意组团互拼。只要硬件性能允许。正好可以进行压力测试。
在任意工作状态时,可以随意暂停或继续某单位的工作。
在任意工作状态时,可以尝试随意关闭某单位的窗体,让它退出。或者在主窗体delete它。
在任意工作状态时,可以直接关闭主窗体,让整个程序退出,并瞬间停止所有工作。
在任意工作状态时,“停止工作”的动作,不能造成野循环和内存泄漏,更不能有时序错误。