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

前言:生产者消费者模式在《大话设计模式》一书中并没有涉及。可奈何在工作中很多地方都要用到。此模式的例子基本都相似,在网上找了一篇博客(原文),在此进行剖析,写此篇博文学习分享之。

本篇博文给出详细解释,相信小白都能看得懂。

储备知识:C++多线程编程、互斥锁、状态变量。

先给出原码:

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

static const int kItemRepositorySize = 3; // 队列大小
static const int kItemsToProduce = 10;   // 待生产的数量
std::mutex mutex;//见注1。
struct ItemRepository
{
	int item_buffer[kItemRepositorySize]; // 见注2
	size_t read_position; // 消费者读取产品位置.见注3
	size_t write_position; // 生产者写入产品位置.
	std::mutex mtx; // 见注4
	std::condition_variable repo_not_full; // 条件变量, 指示产品缓冲区不为满.见注5
	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)//见注6.
	{ 
		{
			std::lock_guard<std::mutex> lock(mutex);//注7
			std::cout << "缓冲区满,等待缓冲区不满\n";
		}
		(ir->repo_not_full).wait(lock); // 注8
	}

	(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);
	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); // 循环生产 kItemsToProduce 个产品.
		{
			std::lock_guard<std::mutex> lock(mutex);
			std::cout << "生产第 " << i << "个产品" << std::endl;
		}
		
	}
}

void ConsumerTask() // 消费者任务
{
	static int cnt = 0;
	while (1)
	{
		std::this_thread::sleep_for(std::chrono::seconds(1));//注9
		int item = ConsumeItem(&gItemRepository); // 消费一个产品.
		{
			std::lock_guard<std::mutex> lock(mutex);
			std::cout << "消费第" << item << "个产品" << std::endl;
		}
	
		if (++cnt == kItemsToProduce) break; // 如果产品消费个数为 kItemsToProduce, 则退出.
	}
}

void InitItemRepository(ItemRepository *ir)
{
	ir->write_position = 0; // 初始化产品写入位置.
	ir->read_position = 0; // 初始化产品读取位置.
}

void main()
{
	InitItemRepository(&gItemRepository);
	std::thread producer(ProducerTask); // 创建生产者线程.
	std::thread consumer(ConsumerTask); // 创建消费之线程.

	producer.join();
	consumer.join();
}

几点解释,大佬请绕行:

注1:此锁是多线程输出同步锁,你会发现在所有的cout上面都有一行加锁的语句std::lock_guard<std::mutex>lock(mutex);

这里的锁是为了防止多个线程向屏幕输出的时候会出现打印错乱交叉的情况。加上此锁,一个线程输出的同时,另一个线程就在等着,这样有效的避免了输出混乱。

注2:这里的整型数组是一个缓冲区,这是生产者消费者的一个重大核心。缓冲区是生产者和消费者数据交互的渠道。生产者生产的数据存入此数组中,然后消费者从此数组中取出数据。

注3:此处可以理解为一个标志位,生产者生产的数据存放到数组中,这不是一个实体可以放进去拿出来,所以弄一个标志位。

注4:此处的锁是操作整型数组缓冲区的锁,和上面输出锁一样,防止多线程操作时候,缓冲区发生混乱。

注5:此处状态变量也算是一个生产者消费者的核心内容之一。生产者生产的数据存满了缓冲区,要进行通知消费者进行消费。

注6:此处是生产是否为满的判断条件,当生产者生产一个数据write_position加1,当write_position为2的时候,说明生产了0、1、2总共三个数据,若缓冲区是3,则(2+1)%3为0,则判断为生产满的状态。

注7:此处注释和注1相呼应,但是有没有发现这里多了一个大括号{},这个大括号是加锁的括号,说明加锁的作用域只在此大括号里面。

注8:当注6的判断条件成立之后,则说明缓冲区满了,然后在这里等待,如果没有收到消费者线程的通知消息,此线程就一直在等待。

注9:这里使用了线程中的延时函数,让消费者线程延时1秒。其目的是让生产者线程在线程起来之后有足够的时间生产数据,因为此函数缓冲区设置为3,所以在及短的时间里缓冲区就满了,而此时消费者再去消费,此时的逻辑是缓冲区永远是差一个数据就发生溢出,然后生产一个数据,缓冲区溢出,通知消费者消费,然后缓冲区不为满,再生产,再溢出,再消费。

 

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
生产者消费者设计模式是一种常用的多线程设计模式,它用于解决生产者消费者之间的数据交互问题。在这种模式中,生产者负责生产数据并将其放入共享队列中,而消费者则负责从队列中取出数据并进行处理。这种设计模式可以有效地实现数据的异步传输和处理,提高系统的并发性和吞吐量。 下面是一个使用C语言实现的生产者消费者示例程序,其中包括了详细的注释解释各个部分的作用和原理: ```c #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define BUFFER_SIZE 10 // 队列缓冲区大小 int buffer[BUFFER_SIZE]; // 共享队列缓冲区 int count = 0; // 共享队列中元素的数量 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 互斥锁 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 条件变量 // 生产者线程函数 void *producer(void *arg) { int item; while (1) { item = rand() % 100; // 随机生成一个数据项 // 获取互斥锁,保证对共享队列的操作是原子性的 pthread_mutex_lock(&mutex); // 如果队列已经满了,则等待条件变量的信号 while (count == BUFFER_SIZE) { pthread_cond_wait(&cond, &mutex); } buffer[count] = item; // 将数据项放入共享队列中 count++; // 增加队列中元素的数量 // 发送条件变量的信号,通知消费者线程可以从队列中取出数据 pthread_cond_signal(&cond); // 释放互斥锁 pthread_mutex_unlock(&mutex); printf("Producer produced %d\n", item); // 打印生产者生产的数据项 } pthread_exit(NULL); } // 消费者线程函数 void *consumer(void *arg) { int item; while (1) { // 获取互斥锁,保证对共享队列的操作是原子性的 pthread_mutex_lock(&mutex); // 如果队列为空,则等待条件变量的信号 while (count == 0) { pthread_cond_wait(&cond, &mutex); } item = buffer[count - 1]; // 从共享队列中取出数据项 count--; // 减少队列中元素的数量 // 发送条件变量的信号,通知生产者线程可以向队列中放入数据 pthread_cond_signal(&cond); // 释放互斥锁 pthread_mutex_unlock(&mutex); printf("Consumer consumed %d\n", item); // 打印消费者消费的数据项 } pthread_exit(NULL); } int main() { pthread_t producer_thread, consumer_thread; // 创建生产者线程和消费者线程 pthread_create(&producer_thread, NULL, producer, NULL); pthread_create(&consumer_thread, NULL, consumer, NULL); // 等待线程结束 pthread_join(producer_thread, NULL); pthread_join(consumer_thread, NULL); return 0; } ``` 在上面的示例程序中,生产者线程和消费者线程共享一个固定大小的队列缓冲区,通过互斥锁和条件变量实现对队列的互斥访问和同步操作。生产者线程随机生成一个数据项,并将其放入共享队列中,然后发送条件变量的信号通知消费者线程可以从队列中取出数据。消费者线程从队列中取出一个数据项,并发送条件变量的信号通知生产者线程可以向队列中放入数据。这样,生产者消费者之间就可以实现同步和互斥操作,避免了数据竞争和死锁等问题。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值