c++11 實現條件變量以及利用條件變量實現多生成者與多消費者模型

自定義條件變量類:

#pragma once
/// <summary>
/// 用C++11實現跨平臺的條件等待類
/// </summary>

#include<functional>
#include<condition_variable>
#include <mutex>

class LanWaitCondition
{
public:
    LanWaitCondition();
    ~LanWaitCondition();

    /// <summary>
    /// 等待
    /// </summary>
    void Wait();

    /// <summary>
    /// 喚醒一個
    /// </summary>
    void NotifyOne();

    /// <summary>
    /// 喚醒所有
    /// </summary>
    void NotifyAll();

private:
    std::mutex m_mutex; //鎖
    std::condition_variable m_cv;   //阻塞等待條件
    int m_waitCount = 0;    //等待次數
    int m_wakeupCount = 0;    //喚醒次數
};


#include "LanWaitCondition.h"

LanWaitCondition::LanWaitCondition()
{
}

LanWaitCondition::~LanWaitCondition()
{
}

void LanWaitCondition::Wait()
{
    std::unique_lock<std::mutex> lock(m_mutex);
    m_cv.wait(lock);
}

void LanWaitCondition::NotifyOne()
{
    m_cv.notify_one();
}

void LanWaitCondition::NotifyAll()
{
    m_cv.notify_all();
}

生產者與消費者模型及其測試代碼

// C++11_ConditionWait.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
//

#include <iostream>
#include <queue>
#include "LanWaitCondition.h"
#include <thread>
#include <chrono>
#include <atomic>

const int con_bufSize = 100000;    //所謂的時間換空間!!!  100M緩存
const int con_perMsgSize = 1024;
char buf[con_bufSize][con_perMsgSize] = { 0 };
std::queue<int> queueForFree;   //當前空閒隊列
LanWaitCondition lwcForFree;    //空閒隊列條件變量
std::mutex mutexForFree;        //空閒隊列鎖
std::queue<int> queueForUse;    //已使用隊列
LanWaitCondition lwcForUse;     //已使用隊列條件變量
std::mutex mutexForUse;         //使用隊列鎖

const int con_productorSize = 3;    //生產者數量
const int con_consumerSize = 8;     //消費者數量
std::vector<std::thread*> vecTD;   //具體業務的線程
bool bExit[con_productorSize + con_consumerSize];  //線程退出標誌  必須要讓每個線程自己把控這個變量!!!
//LanWaitCondition lwcForExit;    //退出條件變量
std::atomic<int> produceMsgCount = 0;
std::atomic<int> consumerMsgCount = 0;

void Stop()
{
    printf("bExit 爲 true   %d!!!!!!!!!!!!!!!!\n", sizeof(bExit) / sizeof(bExit[0]));

    //設置退出標誌爲true
    for (int i = 0; i < sizeof(bExit) / sizeof(bExit[0]); i++)
    {
        bExit[i] = true;
    }

    //清空隊列
    std::queue<int> tempQueue_1;
    mutexForFree.lock();
    queueForFree.swap(tempQueue_1);
    mutexForFree.unlock();
    //清空隊列
    std::queue<int> tempQueue_2;
    mutexForUse.lock();
    queueForUse.swap(tempQueue_2);
    mutexForUse.unlock();
    //通知隊列退出
    lwcForFree.NotifyAll();

    for (int i = 0; i < con_productorSize; i++)
    {
        //確保線程一定退出!!
        while (bExit[i])
        {
            std::chrono::milliseconds durable(60);
            std::this_thread::sleep_for(durable);
            lwcForFree.NotifyAll();
        }
    }

    lwcForUse.NotifyAll();

    for (int i = con_productorSize; i < vecTD.size(); i++)
    {
        //確保線程一定退出!!
        while (bExit[i])
        {
            std::chrono::milliseconds durable(60);
            std::this_thread::sleep_for(durable);
            lwcForUse.NotifyAll();
        }
    }

    //爲了讓子線程先退出,主線程再退出!!
    //for (int i = 0; i < vecTD.size(); i++)
    //{
    //    vecTD[i].join();//這種方式等待退出,在高併發的情況下,有可能會永遠阻塞在這裏!!因爲有些線程可能沒有被真正喚醒!!
    //}

    if (vecTD.empty() == false)
    {
        for (int i = 0; i < vecTD.size(); i++)
        {
            if (vecTD[i]->joinable())
            {
                vecTD[i]->join();   //確保delete時,該線程的狀態爲不能加入類型!!!若delete時線程是可加入的,則會引起崩潰!!!
            }

            delete vecTD[i];
        }

        vecTD.clear();
    }
}

void TestProductor(int id, bool& bThreadExit)
{
    //生產者生產字符
    while (!bThreadExit)
    {
#if 0
        char tem[con_perMsgSize] = { 0 };
        std::cin >> tem;
#else
        //std::chrono::seconds durable(1);
        //std::this_thread::sleep_for(durable);
        std::string str = "6";
        char tem[con_perMsgSize] = {0};
        memcpy(tem, str.c_str(), sizeof(char) * str.length());
#endif
        str = tem;
        mutexForFree.lock();

        if (queueForFree.size() <= 0)
        {
            mutexForFree.unlock();

            if (bThreadExit)
            {
                break;
            }

            lwcForFree.Wait();
            continue;
        }

        int idx = queueForFree.front();
        queueForFree.pop();
        mutexForFree.unlock();
        memcpy(buf[idx], tem, con_perMsgSize);
        //printf("生產者%d生產消息:%s\n", id, tem);
        produceMsgCount++;

        if (!bThreadExit)
        {
            mutexForUse.lock();
            queueForUse.push(idx);
            mutexForUse.unlock();
            lwcForUse.NotifyOne();
        }
    }

    bThreadExit = false;    //表明自己退出了!!
    printf("生產者%d退出!!\n", id);
}

void TestConsumer(int id, bool& bThreadExit)
{
    //消費者消費字符
    while (!bThreadExit)
    {
        bool bLoop = true;

        while (bLoop)
        {
            mutexForUse.lock();

            if (queueForUse.size() <= 0)
            {
                mutexForUse.unlock();
                bLoop = false;
                continue;
            }

            int idx = queueForUse.front();
            queueForUse.pop();
            mutexForUse.unlock();
            char temp[con_perMsgSize] = { 0 };
            memcpy(temp, buf[idx], con_perMsgSize);
            //printf("消費者%d消費消息:%s\n", id, temp);
            consumerMsgCount++;

            if (bThreadExit)
            {
                break;
            }
            else
            {
                mutexForFree.lock();
                queueForFree.push(idx);
                mutexForFree.unlock();
                lwcForFree.NotifyOne();
            }
        }

        //如果是退出
        if (bThreadExit)
        {
            break;
        }

        //printf("消費者%d Wait!!\n", id);
        //必須放在後面!
        lwcForUse.Wait();
        //printf("消費者%d 跳出Wait!!\n", id);
    }

    bThreadExit = false;    //表明自己退出了!!
    printf("消費者%d退出!!\n", id);
}

void InputExit()
{
    std::chrono::seconds durable(3);
    std::this_thread::sleep_for(durable);
    //lwcForExit.NotifyOne();
}

void Test()
{
    for (int i = 0; i < con_bufSize; i++)
    {
        queueForFree.push(i);
    }

    for (int i = 0; i < con_productorSize; i++)
    {
        bExit[i] = false;
        vecTD.emplace_back(new std::thread(TestProductor, i, std::ref(bExit[i])));  //聲明線程時開始,這個線程就開始執行了!!!   //std::ref表示“傳引用”!!
    }

    for (int i = 0; i < con_consumerSize; i++)
    {
        bExit[con_productorSize + i] = false;
        vecTD.emplace_back(new std::thread(TestConsumer, i, std::ref(bExit[con_productorSize + i]))); //聲明線程時開始,這個線程就開始執行了!!!
    }

    //std::thread stopTd(InputExit);
    //stopTd.join();
    //等待指示退出
    //lwcForExit.Wait();
    //退出
    InputExit();
    Stop();
    //打印生產消息總數和消費消息總數
    int tmpPMCount = produceMsgCount;
    int tmpCMCount = consumerMsgCount;
    printf("生產消息總數:%d   消費消息總數:%d\n", tmpPMCount, tmpCMCount);
}

int main()
{
    for (int i = 0; i < 100; i++)
    {
        Test();
        produceMsgCount = 0;
        consumerMsgCount = 0;
        printf("這是第%d次計算 \n", i + 1);
    }

    std::cout << "Hello World!\n";
}


生成結果:
100M緩存,3個生產者10個消費者,程序運行6s
debug版本,生產了87萬多個消息,消費了87萬多消息!!!
在這裏插入圖片描述
release版本,生產了855萬多個消息,消費了855萬多消息!!!
在這裏插入圖片描述
需要注意的是: 使用join方式,等待線程退出,在高併發的場景下有可能導致主線程永遠阻塞着,如下圖所示:
在這裏插入圖片描述
總線程數是24個,還有好幾個消費者線程沒有退出!!
所以,停止所有線程時,需要着重注意,具體請看“Stop”函數的實現!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章