Java与Go: 生产者消费者模型

news2024/10/6 4:08:22

什么是生产者消费者模型

生产者-消费者模型(也称为生产者-消费者问题)是一种常见的并发编程模型,用于处理多线程或多进程之间的协同工作。该模型涉及两个主要角色:生产者和消费者,一个次要角色:缓冲区。

  • 生产者:生产者是生成数据或资源的角色。它将生产的数据或资源放入一个共享缓冲区(如队列)中。

  • 消费者:消费者是消费数据或资源的角色。它从共享缓冲区中获取数据或资源,并进行处理。

生产者和消费者共享一个缓冲区,通过缓冲区进行数据或资源的传递。生产者将数据或资源放入缓冲区,而消费者从缓冲区中取出数据或资源进行处理。

现实案例

例如餐厅订单处理

在一家餐厅中,生产者-消费者模型可以通过厨师(生产者)和服务员(消费者)的角色来表现:

  • 生产者(厨师):厨师负责制作食物。他们接收来自顾客的订单,并开始制作相应的菜肴。制作好的菜肴会放在一个特定的区域(缓冲区),例如出餐台。

  • 消费者(服务员):服务员负责将厨师制作好的菜肴送到顾客的餐桌上。他们从出餐台(缓冲区)中拿取菜肴,并将其送到顾客的桌上。

在这个例子中:

  • 出餐台就像一个共享缓冲区,厨师将制作好的菜肴放在那里,服务员从那里取走。
  • 出餐台有一定的容量限制。厨师在制作新的菜肴之前,需要确保出餐台有足够的空间(缓冲区不满),否则厨师可能会等待一段时间。
  • 服务员在出餐台上拿取菜肴时,也可能遇到出餐台为空的情况。这时,服务员需要等待厨师制作新的菜肴。

通过这个例子,我们可以看到生产者-消费者模型在餐厅中的实际应用。这种模式帮助餐厅协调厨师和服务员之间的工作,从而确保菜肴的制作和服务流程流畅且高效。

问题与解决方案

生产者-消费者模型的主要问题是如何协调生产者和消费者的行为,以避免以下情况:

  • 缓冲区溢出:如果生产者在消费者无法及时消费数据的情况下继续生产,缓冲区可能会变得过满,导致缓冲区溢出。

  • 缓冲区空:如果消费者在生产者无法及时生产数据的情况下继续消费,缓冲区可能会变得空,导致消费者无法继续消费。

为了解决这些问题,生产者和消费者可以使用同步机制,如锁、信号量或条件变量,以确保生产者和消费者在合适的时间进行操作。这些机制可以控制缓冲区的状态,确保生产者和消费者之间的协调工作。

Java实现

可以使用Java 内置的 synchronized 关键字来实现线程同步。通过在共享资源(如 List 缓存区)上使用 synchronized 块或方法,可以确保在操作共享资源时线程的安全性和协调。

import java.util.ArrayList;
import java.util.List;

class Producer implements Runnable {
    private final List<Integer> buffer;
    private final int maxSize;

    public Producer(List<Integer> buffer, int maxSize) {
        this.buffer = buffer;
        this.maxSize = maxSize;
    }

    @Override
    public void run() {
        int count = 0;
        while (true) {
            synchronized (buffer) {
                // 如果缓存区满了,等待
                while (buffer.size() == maxSize) {
                    try {
                        buffer.wait();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        e.printStackTrace();
                    }
                }
                
                // 生产数据
                int data = count++;
                buffer.add(data);
                System.out.println("Producer produced: " + data);
                
                // 唤醒消费者
                buffer.notify();
                
                // 模拟生产数据的时间
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    e.printStackTrace();
                }
            }
        }
    }
}

class Consumer implements Runnable {
    private final List<Integer> buffer;

    public Consumer(List<Integer> buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (buffer) {
                // 如果缓存区空了,等待
                while (buffer.isEmpty()) {
                    try {
                        buffer.wait();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        e.printStackTrace();
                    }
                }
                
                // 从缓存区中取出数据
                int data = buffer.remove(0);
                System.out.println("Consumer consumed: " + data);
                
                // 唤醒生产者
                buffer.notify();
                
                // 模拟消费数据的时间
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    e.printStackTrace();
                }
            }
        }
    }
}

public class ProducerConsumerDemo {
    public static void main(String[] args) {
        // 创建一个缓存区
        List<Integer> buffer = new ArrayList<>();
        int maxSize = 10;

        // 创建生产者和消费者
        Producer producer = new Producer(buffer, maxSize);
        Consumer consumer = new Consumer(buffer);

        // 创建线程
        Thread producerThread = new Thread(producer);
        Thread consumerThread = new Thread(consumer);

        // 启动线程
        producerThread.start();
        consumerThread.start();
    }
}
  • ProducerConsumer 类在操作 List 缓存区时都使用 synchronized 块来进行线程同步。
  • Producer 类中,如果缓存区满了,生产者线程将等待,直到缓存区有空闲空间;在 Consumer 类中,如果缓存区为空,消费者线程将等待,直到缓存区有数据。
  • 使用 buffer.wait()buffer.notify() 进行线程协调。当生产者或消费者等待时,线程会通过 wait() 进入等待状态;当操作完成后,通过 notify() 唤醒对方线程。

这样可以确保在操作 List 缓存区时线程的安全性和协调。

Go实现

在 Go 语言中,使用 sync.Mutex 来同步单生产者单消费者模型中的共享资源。通过 sync.Mutex,你可以确保在操作共享资源时只有一个 goroutine 能够访问,从而避免竞争条件。

package main

import (
	"fmt
	"sync"
	"time"
)

// Producer 负责生产数据并将其放入缓存区
func Producer(buffer *[]int, maxSize int, mu *sync.Mutex, cond *sync.Cond) {
	count := 0
	for {
		mu.Lock()
		// 如果缓存区满了,等待
		for len(*buffer) == maxSize {
			cond.Wait()
		}
		// 生产数据
		data := count
		count++
		*buffer = append(*buffer, data)
		fmt.Println("Producer produced:", data)

		// 唤醒消费者
		cond.Signal()
		mu.Unlock()

		// 模拟生产数据的时间
		time.Sleep(500 * time.Millisecond)
	}
}

// Consumer 负责从缓存区中获取数据并进行消费
func Consumer(buffer *[]int, mu *sync.Mutex, cond *sync.Cond) {
	for {
		mu.Lock()
		// 如果缓存区空了,等待
		for len(*buffer) == 0 {
			cond.Wait()
		}
		// 从缓存区中获取数据
		data := (*buffer)[0]
		*buffer = (*buffer)[1:]
		fmt.Println("Consumer consumed:", data)

		// 唤醒生产者
		cond.Signal()
		mu.Unlock()

		// 模拟消费数据的时间
		time.Sleep(1000 * time.Millisecond)
	}
}

func main() {
	// 创建一个缓存区
	buffer := make([]int, 0)
	maxSize := 10

	// 创建互斥锁和条件变量
	var mu sync.Mutex
	cond := sync.NewCond(&mu)

	// 创建生产者和消费者 goroutine
	go Producer(&buffer, maxSize, &mu, cond)
	go Consumer(&buffer, &mu, cond)

	// 让 main goroutine 等待
	time.Sleep(10 * time.Second)
}

在这个示例中:

  • ProducerConsumer 函数操作共享的缓存区 buffer,以及 sync.Mutex 互斥锁 musync.Cond 条件变量 cond

  • ProducerConsumer 函数中,使用 mu.Lock()mu.Unlock() 来确保在操作共享资源时只有一个 goroutine 能访问。

  • Producer 中,如果缓存区满了,生产者线程将调用 cond.Wait() 进入等待状态,直到缓存区有空闲空间。在 Consumer 中,如果缓存区为空,消费者线程将调用 cond.Wait() 进入等待状态,直到缓存区有数据。

  • Producer 生产数据或 Consumer 消费数据后,分别调用 cond.Signal() 唤醒对方线程。

  • main 函数中,通过 go 关键字分别启动 ProducerConsumer goroutine。最后通过 time.Sleep(10 * time.Second)main goroutine 等待 10 秒钟,以便观察生产者和消费者的行为。

这个模型展示了如何使用 sync.Mutexsync.Cond 来同步单生产者单消费者模型中的共享资源,并确保线程安全。

思考

那么要怎么将上述案例改写成多生产者多消费者模型?

往期推荐

Java与Go:字符串转IP地址

Java与Go:文件IO

Java vs. Go:时间函数

Java与Go:字符串方法

Java与Go:方法和接口

Java与Go:引用和指针

Java与Go:对象

Java与Go:Map

Java 与 Go:可变数组

Java 与 Go:数组

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1642674.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

全方位解析Node.js:从模块系统、文件操作、事件循环、异步编程、性能优化、网络编程等高级开发到后端服务架构最佳实践以及Serverless服务部署指南

Node.js是一种基于Chrome V8引擎的JavaScript运行环境&#xff0c;专为构建高性能、可扩展的网络应用而设计。其重要性在于革新了后端开发&#xff0c;通过非阻塞I/O和事件驱动模型&#xff0c;实现了轻量级、高并发处理能力。Node.js的模块化体系和活跃的npm生态极大加速了开发…

【React】React-redux多组件间的状态传递

效果&#xff08;部分完整代码在最底部&#xff09;&#xff1a; 编写 Person 组件 上面的 Count 组件&#xff0c;已经在前面几篇写过了&#xff0c;也可以直接翻到最底部看 首先我们需要在 containers 文件夹下编写 Person 组件的容器组件 首先我们需要编写 index.jsx 文件…

一种算法分类方式及其应用

在计算机科学领域&#xff0c;算法是解决问题的有效方法&#xff0c;而对算法进行分类有助于理解它们的特性、优劣以及在不同场景下的应用。常见的算法分类方法&#xff0c;包括按设计思想、问题类型、数据结构和应用领域等&#xff0c;每一类算法会对应有其典型和实际应用。 算…

连接HiveMQ代理器实现MQTT协议传输

先下载MQTTX: MQTTX: Your All-in-one MQTT Client Toolbox 使用线上免费的MQTTX BROKER:The Free Global Public MQTT Broker | Try Now | EMQ 打开MQTTX&#xff0c;创建连接&#xff0c;点击NEW SUBSCRIPTION,创建一个主题&#xff0c;这里使用test/topic,在下面Json中填写…

大语言模型教程与实践(开源)

1.简介 大语言模型&#xff08;Large Language Models, LLMs&#xff09;的兴起确实始于OpenAI在2018年发布的GPT&#xff08;Generative Pre-trained Transformer&#xff09;&#xff0c;这一开创性工作引领了自然语言处理领域的新纪元。随后&#xff0c;2022年底ChatGPT的横…

DDD:根据maven的脚手架archetype生成ddd多模块项目目录结构

随着领域驱动的兴起&#xff0c;很多人都想学习如何进行ddd的项目开发&#xff0c;那ddd的项目结构是怎么样的&#xff1f;又是如何结合SpringBoot呢&#xff1f;那么针对这个问题&#xff0c;笔者使用maven的archetype封装一个相对通用的ddd的项目目录&#xff0c;方便一键生成…

Redisson 分布式锁和同步器

系列文章目录 文章目录 系列文章目录前言前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 redisson 是基于redis的扩展库,使得redis除了应用于缓存以外,还能做队列…

Excel 批量创建sheet页

参考资料 最巧妙的Excel批量创建工作表方法 一. 需求 ⏹有如下模板&#xff0c;现想根据提供的姓名&#xff0c;批量创建sheet页&#xff0c;要求每个sheet页拥有相同的模板 二. 通过透视表&#xff0c;批量创建sheet页面 ⏹如下图所示的步骤&#xff0c;创建透视表后&#…

3.3Java全栈开发前端+后端(全栈工程师进阶之路)-前端框架VUE3框架-企业级应用-Vue组合式API

为什么要使用Composition API 一个Options API实例 在前面的课程中&#xff0c;我们都是采用 Options API&#xff08;基于选项的 API &#xff09; 来写一个组件的。下面是一个实例&#xff1a; <template> Count is: {{ count }}, doubleCount is: {{ doubleCount…

brpc中http2 grpc协议解析

搭建gRPC服务 | bRPC https://blog.csdn.net/INGNIGHT/article/details/132657099 global.cpp http2_rpc_protocol.cpp ParseH2Message解析frame header信息 ParseResult H2Context::ConsumeFrameHead( 这个是固定长度的9字节帧头部&#xff0c;length是&#xff0c;3*8bit…

七. Django项目之电商购物商城 -- 退出登录

Django项目之电商购物商城 – 退出登录状态 需要开发文档和前端资料的可私聊 退出登录主要是基于Django自带的logout模块 , 该功能只有在登录是保存了用户状态才可以实现调用 一. 创建退出视图 class LogoutView(View):def get(self , request):# 删除用户数据logout(reque…

etcd源码流程---调试环境的搭建

etcd启动命令&#xff1a; name必须设置&#xff0c;否则会用default&#xff0c;集群内不同etcd实例的名字应该是唯一的&#xff0c;因为他会有一个map(name->ip)。如果initial-cluster-state设置为new&#xff0c;那么他会创建一个新的clusterid。需要在initial-cluster中…

PHP 反序列化

一、PHP 序列化 1、对象的序列化 <?php class people{public $nameGaming;private $NationLiyue;protected $Birthday12/22;public function say(){echo "老板你好呀&#xff0c;我是和记厅的镖师&#xff0c;叫我嘉明就行&#xff0c;要运货吗你&#xff1f;"…

【LeetCode:1235. 规划兼职工作 + 动态规划 + 二分查找】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

Unity---版本控制软件

13.3 版本控制——Git-1_哔哩哔哩_bilibili Git用的比较多 Git 常用Linux命令 pwd&#xff1a;显示当前所在路径 ls&#xff1a;显示当前路径下的所有文件 tab键自动补全 cd&#xff1a;切换路径 mkdir&#xff1a;在当前路径下创建一个文件夹 clear&#xff1a;清屏 vim…

能将图片转为WebP格式的WebP Server Go

本文完成于 2023 年 11 月 之前老苏介绍过 webp2jpg-online&#xff0c;可以将 webp 格式的图片&#xff0c;转为 jpg 等&#xff0c;今天介绍的 WebP Server Go 是将 jpg 等转为 webp 格式 文章传送门&#xff1a;多功能图片转换器webp2jpg-online 什么是 WebP ? WebP 它是由…

SpringBoot与SpringMVC的区别

SpringBoot与SpringMVC的区别是什么&#xff1f; SpringBoot和SpringMVC是Java开发中常用的两个框架&#xff0c;它们都是由Spring框架所提供的&#xff0c;但在功能和使用方式上有着一些区别。本文将分别介绍SpringBoot和SpringMVC的特点和区别。 一、SpringBoot的特点&#…

区域文本提示的实时文本到图像生成;通过一致性自注意力机制的视频生成工具保持视频的一致性;专门为雪佛兰汽车设计的客服聊天机器人

✨ 1: StreamMultiDiffusion StreamMultiDiffusion是首个基于区域文本提示的实时文本到图像生成框架&#xff0c;实现了高速且互动的图像生成。 StreamMultiDiffusion 旨在结合加速推理技术和基于区域的文本提示控制&#xff0c;以克服之前解决方案中存在的速度慢和用户交互性…

Pytorch: nn.Embedding

文章目录 1. 本质2. 用Embedding产生一个10 x 5 的随机词典3. 用这个词典编码两个简单单词4. Embedding的词典是可以学习的5. 例子完整代码 1. 本质 P y t o r c h \mathrm{Pytorch} Pytorch 的 E m b e d d i n g \mathrm{Embedding} Embedding 模块是一个简单的查找表&#…

活动图与状态图:UML中流程图的精细化表达——专业解析系统动态性与状态变迁

流程图是一种通用的图形表示法&#xff0c;用以展示步骤、决策和循环等流程控制结构。它通常用于描述算法、程序执行流程或业务过程&#xff0c;关注于任务的顺序执行。流程图强调顺序、分支和循环&#xff0c;适用于详细说明具体的处理步骤&#xff0c;图形符号相对基础和通用…