生產者——消費者問題(producer-consumer),又名:有界緩衝區(bounded-buffer)問題
多生產者——單消費者
設計要點:
當緩衝區滿已滿,而此時生產者還想向緩衝區中放入一個新的數據項時。則讓生產者睡眠,待消費者從緩衝區中取出一個或多個數據項時再喚醒生產者。
同時,多個生產者,則需要多添加一個條件變量和計數變量,用來保證多個生產者之間對消息隊列的互斥訪問。
當緩衝區滿已空,而此時消費者還想從緩衝區中取出一個新的數據項時。則讓消費者睡眠,待生產者向緩衝區中放入一個或多個數據項時再喚醒消費者。
代碼實現如下:(IDE : VS2017)
#include"stdafx.h"
#include<string>
#include<stdio.h>
#include <windows.h>//Sleep()函數需要包含的頭文件
#include <stdlib.h>
#include<thread>
#include <mutex>
#include<iostream>
using namespace std;
static const int kItemRepositorySize = 10; // Item buffer size.
static const int kItemsToProduce = 100; // How many items we plan to produce.
class ItemRepository
{
public:
int item_buffer[kItemRepositorySize]; // 產品緩衝區, 配合 read_position 和 write_position 模型環形隊列.
size_t read_position; // 消費者讀取產品位置.
size_t write_position; // 生產者寫入產品位置.
size_t item_counter; //計數變量
std::mutex mtx; // 互斥量,保護產品緩衝區
std::mutex item_counter_mtx;//互斥量,保證多個生產者對緩衝區的互斥訪問
std::condition_variable repo_not_full; // 條件變量, 指示產品緩衝區不爲滿.
std::condition_variable repo_not_empty; // 條件變量, 指示產品緩衝區不爲空.
}gItemRepository; // 產品庫全局變量, 生產者和消費者操作該變量.
//生產者函數不需要特殊處理
void ProduceItem(ItemRepository *ir, int item)
{
std::unique_lock<std::mutex> lock(ir->mtx);
while (((ir->write_position + 1) % kItemRepositorySize) == ir->read_position)
{ // item buffer is full, just wait here.
std::cout << "Producer is waiting for an repo_not_full notification...\n";
(ir->repo_not_full).wait(lock); // 生產者等待"產品庫緩衝區不爲滿"這一條件發生.
}
(ir->item_buffer)[ir->write_position] = item; // 寫入產品.
(ir->write_position)++; // 寫入位置後移.
std::cout << "Producer thread: " << std::this_thread::get_id() << "is producing the:" << item << "^th item..." << std::endl;
(gItemRepository.item_counter)++;
if (ir->write_position == kItemRepositorySize) // 寫入位置若是在隊列最後則重新設置爲初始位置.
ir->write_position = 0;
(ir->repo_not_empty).notify_all(); // 通知消費者產品庫不爲空.
lock.unlock(); // 解鎖.
}
int ConsumeItem(ItemRepository *ir)
{
int data;
std::unique_lock<std::mutex> lock(ir->mtx);
// item buffer is empty, just wait here.
while (ir->write_position == ir->read_position)
{
std::cout << "Consumer is waiting for an repo_not_empty notification...\n";
(ir->repo_not_empty).wait(lock); // 消費者等待"產品庫緩衝區不爲空"這一條件發生.
}
/*std::cout << "Consumer is consuming the " << ir->read_position << "^th item" << std::endl;*/
data = (ir->item_buffer)[ir->read_position]; // 讀取某一產品
(ir->read_position)++; // 讀取位置後移
std::cout << "Consumer thread " << std::this_thread::get_id()
<< " is consuming the " << data << "^th item" << std::endl;
if (ir->read_position >= kItemRepositorySize) // 讀取位置若移到最後,則重新置位.
ir->read_position = 0;
(ir->repo_not_full).notify_all(); // 通知生產者產品庫不爲滿.
lock.unlock(); // 解鎖.
return data; // 返回產品.
}
void ProducerTask() // 生產者任務
{
bool ready_to_exit = false;
while (1)
{
Sleep(1);
std::unique_lock<std::mutex>lock(gItemRepository.item_counter_mtx);
if (gItemRepository.item_counter<kItemsToProduce)
{
ProduceItem(&gItemRepository, gItemRepository.item_counter);
}
else ready_to_exit = true;
lock.unlock();
if (ready_to_exit == true){
break;
}
}
}
//多個消費者會對緩衝區進行讀寫,所以需要特殊處理
void ConsumerTask() // 消費者任務
{
static int cnt = 0;
while (1)
{
//sleep(1);
int item = ConsumeItem(&gItemRepository); // 消費一個產品.
if (++cnt == kItemsToProduce) break; // 如果產品消費個數爲 kItemsToProduce, 則退出.
}
}
void InitItemRepository(ItemRepository *ir)
{
ir->write_position = 0; // 初始化產品寫入位置.
ir->read_position = 0; // 初始化產品讀取位置.
ir->item_counter = 0; // 初始化計數位置.
}
int main()
{
//ItemRepository gItemRepository;
InitItemRepository(&gItemRepository);
std::thread producer1(ProducerTask); // 創建生產者線程.
std::thread producer2(ProducerTask);// 創建消費之線程.
std::thread producer3(ProducerTask);
std::thread producer4(ProducerTask);
std::thread consumer(ConsumerTask);
producer1.join();
producer2.join();
producer3.join();
producer4.join();
consumer.join();
system("pause");
return 0;
}