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

2023-05-16

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

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

储备知识: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,所以在及短的时间里缓冲区就满了,而此时消费者再去消费,此时的逻辑是缓冲区永远是差一个数据就发生溢出,然后生产一个数据,缓冲区溢出,通知消费者消费,然后缓冲区不为满,再生产,再溢出,再消费。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++设计模式七、生产者消费者模式(单生产单消费)。 的相关文章

  • SpringBoot 注解大全

    一 注解 annotations 列表 1 64 SpringBootApplication 包含了 64 ComponentScan 64 Configuration和 64 EnableAutoConfiguration注解 其中 64
  • Spring 中的bean 是否线程安全

    结论 xff1a 不是线程安全的 Spring容器中的Bean是否线程安全 xff0c 容器本身并没有提供Bean的线程安全策略 xff0c 因此可以说Spring容器中的Bean本身不具备线程安全的特性 xff0c 但是具体还是要结合具体
  • SpringBoot使用PageHelper分页

    一 开发准备 1 开发工具 IntelliJ IDEA 2020 2 3 2 开发环境 Red Hat Open JDK 8u256 Apache Maven 3 6 3 3 开发依赖 SpringBoot lt dependency gt
  • Windows Server 出现多个匿名登陆用户的问题解决

    1 起因 工作中需要在同一台 windows server的机器上多个用户同时使用 xff0c 遂建立多个账号 xff0c 供大家进行使用 2 问题 一段时间后发现系统特别卡顿并会死机 xff0c 查询原因后发现 xff0c 如图所示 xf
  • java锁 synchronized的使用及原理剖析

    synchronized用法有三个 修饰实例方法 修饰静态方法 修饰代码块 1 修饰实例方法 synchronized关键词作用在方法的前面 xff0c 用来锁定方法 xff0c 其实默认锁定的是this对象 public class Th
  • 面试HashMap的原理

    一般来说 xff0c java面试必不可少的菜品 xff0c 那就是 来 xff0c 讲一下HashMap的原理 那么今天就来讲一下HashMap的原理 先说一下JDK1 7跟JDK1 8对它的改变 JDK1 7之前使用的是数组加链表 xf
  • JAVA开发环境配置

    1 自己在网上下载JDK xff0c 本教程使用JDK1 6 下载好JDK后双击运行 xff0c 然后根据提示进行安装 安装好JDK后 bin xff1a 存放java可执行文件 如 xff1a javac exe java exe等等 d
  • MyEcplise_Maven搭建SSM框架

    Maven源码 链接 xff1a https pan baidu com s 1eTQMJQy 密码 xff1a 8j1q 博文中的MyEcplise 链接 xff1a https pan baidu com s 1dEdQYa 密码 xf
  • 怎么使用Linux常用命令大全

    系统信息 arch 显示机器的处理器架构 1 uname m 显示机器的处理器架构 2 uname r 显示正在使用的内核版本 dmidecode q 显示硬件系统部件 SMBIOS DMI hdparm i dev hda 罗列一个磁盘的
  • MySQL常用语句详解

    Winfrom连接网页 第一种方法 xff1a 调用本地浏览器System Diagnostics Process Start 34 https www microsoft com zh cn 34 第二种方法 xff1a 连接 strin
  • Maven搭建SSH连接Oracle数据库

    Maven工程搭建SSH连接Oracle数据库 首先在pom xml里引入jar lt project xmlns 61 34 http maven apache org POM 4 0 0 34 xmlns xsi 61 34 http
  • MyBatis简介与运用

    1 Mybatis简介 1 1 Mybatis是什么 Mybatis是一个java的持久层框架 xff0c 保存到数据库 持久化 xff1a 保存到本地文件 1 2 Mybatis的作用 操作数据库 1 3 为什么要学习mybatis 1
  • SpringMVC入门原理

    1 Springmvc原理 1 1 什么是springmvc SpringMVC是一个Spring框架内置的对MVC模式的实现 xff0c 就spring的一个子模块 1 2 什么是mvc Model view controller 模型
  • MyBatis逆向工程建立实体

    下面是用MyEcplise开发工具 为例 使用Ecplise操作步骤雷同于MyEcplise 1 第一步 2 搜索MyBatis 等待装载完成 xff0c 完成后 3 创建一个web项目 创建包 xff0c 创建generatorConfi
  • python apscheculer 报错 skipped: maximum number of running instances reached (1)

    apscheduler定时任务报错skipped maximum number of running instances reached 1 原因是默认max instances最大定时任务是1个 xff0c 可以通过在add job中调m
  • java 反射很重要

    1 创建一个User类 public class User private String username private String password private String name public User public Use
  • Netty入门案例教程

    1 首先导入netty all 5 0 0 Alpha1 jar 2 创建一个NettyConfig 整个工程的全局配置 package websocketcom netty import io netty channel group Ch
  • 微信公众号分享的坑

    记一次微信公众号分享sdk 这里我的脚本是用jquery写的 xff0c 不带框架源码 首先创建jsp引入JavaScript微信分享js lt script type 61 34 text javascript 34 src 61 34
  • java linux部署web项目详解

    下载SecureCRT连接linux xff0c 激活SecureCRT跟怎么连接自行百度喽 xff0c 下面开始操作linux服务器 1 查看安装的jdk rpm qa grep java 2 如果有旧的jdk xff0c 就卸载jdk
  • 人工智能如何可以思考?

    近日在给同事讲人工智能的时候 xff0c 提到当数据量不够的时候 xff0c 必要时需要加入人工工程 xff0c 引导计算机 归纳 一些知识 xff0c 毕竟计算机智能比起人类智能 xff0c 最大的缺陷可能在于不懂得 举一反三 换句话说

随机推荐