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

什么是生产者消费者模型

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

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

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

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

现实案例

例如餐厅订单处理

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

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

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

在这个例子中:

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

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

问题与解决方案

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

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

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

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

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:数组

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Unity---版本控制软件

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

EtherCAT通信总线状态监视

1、EtherCAT总线运动控制学习笔记 EtherCAT总线运动控制学习笔记(RXXW_Dor)_汇川pdo控制命令607a-CSDN博客文章浏览阅读3.3k次,点赞3次,收藏9次。说到总线控制,就要说到报文、对象字典、PN通信我们大部分会说报文,EtherCAT通信我们常说对象字典,叫法不一样,但是原理基…

OneFlow深度学习框原理、用法、案例和注意事项

本文将基于OneFlow深度学习框架&#xff0c;详细介绍其原理、用法、案例和注意事项。OneFlow是由中科院计算所自动化研究所推出的深度学习框架&#xff0c;专注于高效、易用和扩展性强。它提供了一种类似于深度学习库的接口&#xff0c;可以用于构建神经网络模型&#xff0c;并…

数据结构---单链表

题目&#xff1a;构造一个单链表。 使用的软件&#xff1a;VS2022使用的语言&#xff1a;C语言使用的项目&#xff1a;test.c Setlist.h Setlish.c 项目实践&#xff1a; Setlist.h的代码为&#xff1a; #pragma once#include<stdio.h> #include<stdlib.h> #incl…

SQL注入基础-3

一、宽字节注入 1、宽字节&#xff1a;字符大小为两个及以上的字节&#xff0c;如GBK&#xff0c;GB2312编码 2、数据库使用GBK编码时&#xff0c;会将两个字符合并为一个汉字(宽字节)。特殊值字符如单引号都会被转义【--->\】&#xff0c;如sqli-lads第32关&#xff0c;输…

【C++】学习笔记——vector_2

文章目录 七、vector2. vecotr的使用3. vector的模拟实现 未完待续 七、vector 2. vecotr的使用 上节我们以二维数组结束&#xff0c;这一节我们以二维数组开始。 // 二维数组 vector<vector<int>> vv;二维数组在底层是连续的一维数组。vv[i][j] 是怎样访问的&a…

Sarcasm detection论文解析 |使用基于多头注意力的双向 LSTM 进行讽刺检测

论文地址 论文地址&#xff1a;https://ieeexplore.ieee.org/document/8949523 论文首页 笔记框架 使用基于多头注意力的双向 LSTM 进行讽刺检测 &#x1f4c5;出版年份:2020 &#x1f4d6;出版期刊:IEEE Access &#x1f4c8;影响因子:3.9 &#x1f9d1;文章作者:Kumar Avinas…

第11章 软件工程

这里写目录标题 1.软件过程1.1能力成熟度模型(CMM)1.2能力成熟度模型集成(CMMI)1.3瀑布模型(线性顺序)1.4增量模型1.5演化模型1.5.1原型模型1.5.2螺旋模型 1.6喷泉模型1.7统一过程(UP)模型 2.敏捷方法3.系统设计4.系统测试4.1单元测试(模块测试)4.2集成测试4.3黑盒测试(功能测试…

论文辅助笔记:Tempo之modules/prompt.py

1 get_prompt_param_cls 2 get_prompt_value 3 Prompt 类 3.1 _init_weights 3.2 forward

一、RocketMQ基本概述与部署

RocketMQ基本概述与安装 一、概述1.MQ概述1.1 用途1.2 常见MQ产品1.3 MQ常用的协议 2.RocketMQ概述2.1 发展历程 二、相关概念1.基本概念1.1 消息&#xff08;Message&#xff09;1.2 主题&#xff08;Topic&#xff09;1.3 标签&#xff08;Tag&#xff09;1.4 队列&#xff0…

gige工业相机突破(一,准备资源)

gige相机能不能绕开相机生产商提供的sdk&#xff0c;而直接取到像&#xff1f; 两种办法&#xff0c;第一&#xff0c;gige vision2.0说明书&#xff0c;第二&#xff0c;genicam 首先你会去干什么事&#xff1f; 好几年&#xff0c;我都没有突破&#xff0c;老虎吃天&#x…

产品AB测试设计

因为vue2项目升级到vue3经历分享1&#xff0c;vue2项目升级到vue3经历分享2&#xff0c;前端系统升级&#xff0c;界面操作也发生改变&#xff0c;为了将影响降到最低&#xff0c;是不能轻易让所有用户使用新系统的。原系统使用好好的&#xff0c;如果新界面用户不喜欢&#xf…

2024/5/5 英语每日一段

Meanwhile, in a twist, Tesla this month settled a high-profile case in Northern California that claimed Autopilot played a role in the fatal crash of an Apple engineer, Walter Huang. The company’s decision to settle with Huang’s family—along with a ruli…

如何打包Apk适配32和64位

一个表格了解lib下的文件夹 .so文件描述armeabi-v7a第七代及以上的ARM处理器&#xff0c;2011年以后生产的大部分Android设备都使用。arm64-v8a第8代、64位ARM处理器&#xff0c;很少设备&#xff0c;三星GalaxyS6是其中之一。armeabi第5代、第6代的ARM处理器&#xff0c;早期…

Mysql报错红温集锦(一)(ipynb配置、pymysql登录、密码带@、to_sql如何加速、触发器SIGNAL阻止插入数据)

一、jupyter notebook无法使用%sql来添加sql代码 可能原因&#xff1a; 1、没装jupyter和notebook库、没装ipython-sql库 pip install jupyter notebook ipython-sql 另外如果是vscode的话还需要安装一些相关的插件 2、没load_ext %load_ext sql 3、没正确的登录到mysql…

Git-flow分支管理与Aone-flow分支管理对比

Git-flow分支管理与Aone-flow分支管理对比 git-flow分支管理&#xff1a; master: 主分支&#xff0c;主要用来版本发布。 hotfix&#xff1a;线上 bug 紧急修复用到的临时分支。这个分支用来修复主线master的BUG release&#xff08;预发布分支&#xff09;&#xff1a;rel…

深入理解网络原理2----UDP协议

文章目录 前言一、UDP协议协议段格式&#xff08;简图&#xff09;校验和 二、UDP与TCP 前言 随着时代的发展&#xff0c;越来越需要计算机之间互相通信&#xff0c;共享软件和数据&#xff0c;即以多个计算机协同⼯作来完成业务&#xff0c;就有了⽹络互连。 一、UDP协议 协…

基于matlab GUI的Alpha shapes边缘提取

1、程序介绍 本程序是基于matlab语言&#xff0c;使用alpha shapes算法实现点云边缘提取。算法具体原理参考博客&#xff1a;基于alpha shapes的边缘点提取&#xff08;matlab&#xff09;-CSDN博客。该程序包括3个按钮&#xff1a;加载点云、边缘点提取、保存。其中&#xff0…

A Bug‘s Life (并查集)

//新生训练 #include <iostream> #include <algorithm> using namespace std; const int N 5000; int p[N], sz[N]; int n, m; int find(int x) {if (p[x] ! x)p[x] find(p[x]);return p[x]; } int main() {int T;scanf("%d", &T);for (int k 1; …

c#学习基础1

一、复杂数据类型 1&#xff09;概述 2&#xff09;枚举 1.基本概念 枚举是一个比较特别的存在&#xff0c;它是一个被命名的整形常量的集合&#xff0c;一般用它来表示状态&#xff0c;类型等 1.1申明枚举和申明枚举变量 1.2申明枚举语法 2.在哪里申明枚举 3.枚举的使用 4…