【Qt线程-5】生产者&消费者模型应用(多态,子线程控制,协同,事件循环)

背景:

个人学习多线程控制,写了一些博文用于记录:

【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它。

在任意工作状态时,可以直接关闭主窗体,让整个程序退出,并瞬间停止所有工作。

在任意工作状态时,“停止工作”的动作,不能造成野循环和内存泄漏,更不能有时序错误。

完结。

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值