C++设计模式八、生产者消费者模式(单生产多消费)。

前言:上篇博文(点击这里)介绍了生产者消费者模式中的单生产单消费模式,本篇博文介绍单生产多消费者模式,下面还会介绍多生产单消费模式、多生产多消费模式。代码类似,所以类似的博客只介绍差异性,本篇纯属小白教学,如有不对,还望指正。如有疑问,可交流学习。

#include <cstdlib>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>

static const int kItemRepositorySize = 5; // Item buffer size.
static const int kItemsToProduce = 10;   // How many items we plan to produce.
std::mutex mutex;//多线程标准输出同步锁
struct ItemRepository
{
	int item_buffer[kItemRepositorySize];
	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;

typedef struct ItemRepository ItemRepository;

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::lock_guard<std::mutex> lock(mutex);
			std::cout << "缓冲区满,等待缓冲区不满\n";
		}
		(ir->repo_not_full).wait(lock);
	}

	(ir->item_buffer)[ir->write_position] = item;
	(ir->write_position)++;

	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::lock_guard<std::mutex> lock(mutex);
			std::cout << "缓冲区空,等待生产者生产产品\n";
		}
		(ir->repo_not_empty).wait(lock);
	}

	data = (ir->item_buffer)[ir->read_position];
	(ir->read_position)++;

	if (ir->read_position >= kItemRepositorySize)
		ir->read_position = 0;

	(ir->repo_not_full).notify_all();
	lock.unlock();

	return data;
}


void ProducerTask()
{
	for (int i = 1; i <= kItemsToProduce; ++i)
	{
		
		ProduceItem(&gItemRepository, i);
		{
			std::lock_guard<std::mutex> lock(mutex);
			std::cout << "生产线程" << std::this_thread::get_id()
				<< "生产第" << i << "个产品" << std::endl;
		}
	}
	{
		std::lock_guard<std::mutex> lock(mutex);
		std::cout << "生产线程" << std::this_thread::get_id()
			<< "退出" << std::endl;
	}
}

void ConsumerTask()
{
	bool ready_to_exit = false;
	int cent = 0;
	while (1)
	{
		std::this_thread::sleep_for(std::chrono::seconds(1));
		std::unique_lock<std::mutex> lock(gItemRepository.item_counter_mtx);
		if (gItemRepository.item_counter < kItemsToProduce)
		{
			int item = ConsumeItem(&gItemRepository);
			++(gItemRepository.item_counter);
			{
				std::lock_guard<std::mutex> lock(mutex);
				std::cout << "消费线程" << std::this_thread::get_id()
					<< "正在消费第" << item << "个产品" << std::endl;
			}
		}
		else
		{
			ready_to_exit = true;
		}
		lock.unlock();
		if (ready_to_exit == true)
		{
			break;
		}
	}
	{
		std::lock_guard<std::mutex> lock(mutex);
		std::cout << "消费线程" << std::this_thread::get_id()
			<< "退出" << std::endl;
	}
}

void InitItemRepository(ItemRepository *ir)
{
	ir->write_position = 0;
	ir->read_position = 0;
	ir->item_counter = 0;
}

int main()
{
	InitItemRepository(&gItemRepository);
	std::thread producer(ProducerTask);
	std::thread consumer1(ConsumerTask);
	std::thread consumer2(ConsumerTask);
	std::thread consumer3(ConsumerTask);
	std::thread consumer4(ConsumerTask);

	producer.join();
	consumer1.join();
	consumer2.join();
	consumer3.join();
	consumer4.join();
	return 0;

}

几点解释,大佬绕行。

有同学可能会想到,我在单生产单消费的main函数中加上几个消费者不就可以了吗???下面解释一下为什么不可以。从上篇你可以看出,当生产者生产数量等于给定的产品数量kItemsToProduce时候,生产者函数会将ir->write_position = 0;,在消费者线程中如果消费全部商品后会将ir->read_position = 0;,若只是简单的加上几个线程,会出现某一个线程消费完最后一个产品的时候,由于ir->read_position是全局变量,所以另外几个消费者线程会进入while (ir->write_position == ir->read_position)循环中,按照预定的生产产品数量,生产者已经生产结束。消费者却进入此循环出不来,一直等待生产者生产,从而造成消费者线程永不会退出,这样会消费大量的系统资源,导致系统崩溃。

从main函数可以看到,有一个生产者和四个消费者线程,这也就是体现了单生产多消费模式。代码和单生产单消费相似,最大的不同是多了一个准备退出ready_to_exit标志位。下面简单讲讲为什么会多出这个操作。

不知你是否注意到上篇单生产单消费模式博文中,在ConsumerTask函数中定义了一个局部变量cnt,消费一个产品,变量加1,直到消费数量达到生产的总数之后,break退出消费者线程。

但是在单生产多消费模式中,出现了多个消费者线程,多线程公用消费者模式,所以要进行加锁处理std::unique_lock<std::mutex> lock(gItemRepository.item_counter_mtx);,也就是说当一个线程消费某一个具体产品的时候,要保证这个产品就不能被其他消费者线程消费。由于线程之间是并行运行的,不知道是哪一个线程消费最后一个产品,所以当消费完最后一个产品之后,将标志位置为true,解锁之后,退出消费者线程。

 

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值