简介:介绍生产者消费者模型,及go简单实现的demo。

一、生产者消费者模型

生产者消费者模型:某个模块(函数等〉负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、协程、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。

单单抽象出生产者和消费者,还够不上是生产者消费者模型。该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据。大概的结构如下图。

假设你要寄一件快递,大致过程如下。.

1.把快递封好——相当于生产者制造数据。

2.把快递交给快递中心——相当于生产者把数据放入缓冲区。

3.邮递员把快递从快递中心取出——相当于消费者把数据取出缓冲区。

这么看,有了缓冲区就有了以下好处:

解耦:降低消费者和生产者之间的耦合度。有了快递中心,就不必直接把快递交给邮寄员,邮寄快递的人不对邮寄员产生任何依赖,如果某一个天邮寄员换人了,对于邮寄快递的人也没有影响。假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。将来如果消费者的代码发生变化,可能会真接影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合度也就相应降低了。

并发:生产者消费者数量不对等,依然能够保持正常通信。由于函数调用是同步的(或者叫阻塞的),在消费者的方法没有返回之前,生产者只好一直等在那边。万一消费者处理数据很慢,生产者只能等着浪费时间。使用了生产者消费者模式之后,生产者和消费者可以是两个独立的并发主体。生产者把制造出来的数据往缓冲区一丢,就可以再去生产下一个数据。基本上不用依赖消费者的处理速度。邮寄快递的人直接把快递扔个快递中心之后就不用管了。

缓存:生产者消费者速度不匹配,暂存数据。如果邮寄快递的人一次要邮寄多个快递,那么邮寄员无法邮寄,就可以把其他的快递暂存在快递中心。也就是生产者短时间内生产数据过快,消费者来不及消费,未处理的数据可以暂时存在缓冲区中。

二、Go语言实现

单向channel最典型的应用是“生产者消费者模型”。channel又分为有缓冲和无缓冲channel。channel中参数传递的时候,是作为引用传递。

1、无缓冲channel

示例代码一实现如下

package main

import "fmt"

func producer(out chan <- int) {
for i:=0; i<10; i++{
data := i*i
fmt.Println("生产者生产数据:", data)
out <- data // 缓冲区写入数据
}
close(out) //写完关闭管道
} func consumer(in <- chan int){
// 同样读取管道
//for{
// val, ok := <- in
// if ok {
// fmt.Println("消费者拿到数据:", data)
// }else{
// fmt.Println("无数据")
// break
// }
//} // 无需同步机制,先做后做
// 没有数据就阻塞等
for data := range in {
fmt.Println("消费者得到数据:", data)
} } func main(){
// 传参的时候显式类型像隐式类型转换,双向管道向单向管道转换
ch := make(chan int) //无缓冲channel
go producer(ch) // 子go程作为生产者
consumer(ch) // 主go程作为消费者
}

这里使用无缓冲channel,生产者生产一次数据放入channel,然后消费者从channel读取数据,如果没有只能等待,也就是阻塞,直到管道被关闭。所以宏观是生产者消费者同步执行。

另外:这里是只而外开辟一个go程执行生产者,主go程执行消费者,如果也是用一个新的go程执行消费者,就需要阻塞main函数中的go程,否则不等待消费者和生产者执行完毕,主go程退出,程序直接结束,如示例代码三。

生产者每一次生产,消费者也只能拿到一次数据,缓冲区作用不大。结果如下:

2、有缓冲channel

示例代码二如下

package main

import "fmt"

func producer(out chan <- int) {
for i:=0; i<10; i++{
data := i*i
fmt.Println("生产者生产数据:", data)
out <- data // 缓冲区写入数据
}
close(out) //写完关闭管道
} func consumer(in <- chan int){ // 无需同步机制,先做后做
// 没有数据就阻塞等
for data := range in {
fmt.Println("消费者得到数据:", data)
} } func main(){
// 传参的时候显式类型像隐式类型转换,双向管道向单向管道转换
ch := make(chan int, 5) // 添加缓冲区,5 go producer(ch) // 子go程作为生产者
consumer(ch) // 主go程作为消费者
}

有缓冲channel,只修改ch := make(chan int, 5) // 添加缓冲一句,只要缓冲区不满,生产者可以持续向缓冲区channel放入数据,只要缓冲区不为空,消费者可以持续从channel读取数据。就有了异步,并发的特性。

结果如下:



这里之所以终端生产者连续打印了大于缓冲区容量的数据,是因为终端打印属于系统调用也是有延迟的,IO操作的时候,生产者同时向管道写入,请求打印,管道的写入读取与终端输出打印速度不匹配。

三、实际应用

实际应用中,同时访问同一个公共区域,同时进行不同的操作。都可以划分为生产者消费者模型,比如订单系统。

很多用户的订单下达之后,放入缓冲区或者队列中,然后系统从缓冲区中去读来真正处理。系统不必开辟多个线程来对应处理多个订单,减少系统并发的负担。通过生产者消费者模式,将订单系统与仓库管理系统隔离开,且用户可以随时下单(生产数据)。如果订单系统直接调用仓库系统,那么用户单击下订单按钮后,要等到仓库系统的结果返回。这样速度会很慢。

也就是:用户变成了生产者,处理订单管理系统变成了消费者。

代码示例三如下

package main

import (
"fmt"
"time"
) // 模拟订单对象
type OrderInfo struct {
id int
} // 生产订单--生产者
func producerOrder(out chan <- OrderInfo) {
// 业务生成订单
for i:=0; i<10; i++{
order := OrderInfo{id: i+1}
fmt.Println("生成订单,订单ID为:", order.id)
out <- order // 写入channel
}
// 如果不关闭,消费者就会一直阻塞,等待读
close(out) // 订单生成完毕,关闭channel
} // 处理订单--消费者
func consumerOrder(in <- chan OrderInfo) {
// 从channel读取订单,并处理
for order := range in{
fmt.Println("读取订单,订单ID为:", order.id)
}
} func main() {
ch := make(chan OrderInfo, 5)
go producerOrder(ch)
go consumerOrder(ch)
time.Sleep(time.Second * 2)
}

这里如上面逻辑类似,不同的是用一个,OrderInfo结构体模拟订单作为业务处理对象。主线程使用time.Sleep(time.Second * 2)阻塞,否则,程序立即停止。

结果如下:

生产者消费者模型及Golang简单实现的更多相关文章

  1. Java多线程(九):生产者消费者模型

    生产者消费者模型 生产者:生产任务的个体: 消费者:消费任务的个体: 缓冲区:是生产者和消费者之间的媒介,对生产者和消费者解耦. 当 缓冲区元素为满,生产者无法生产,消费者继续消费: 缓冲区元素为空, ...

  2. Golang 入门系列(十七)几个常见的并发模型——生产者消费者模型

    前面已经讲过很多Golang系列知识,包括并发,锁等内容,感兴趣的可以看看以前的文章,https://www.cnblogs.com/zhangweizhong/category/1275863.ht ...

  3. golang实现生产者消费者模型

    生产者消费者模型分析 操作系统中的经典模型,由若干个消费者和生产者,消费者消耗系统资源,生产者创造系统资源,资源的数量要保持在一个合理范围(小于数量上限,大约0).而消费者和生产者是通过并发或并行方式 ...

  4. Python-生成器实现简单的"生产者消费者"模型

    一.使用生成器实现简单的生产者消费者模型, 1.效果截屏 代码如下: import time def consumer(name): print('%s 开始买手机' %name) while Tru ...

  5. 使用队列queue实现一个简单的生产者消费者模型

    一.生产者消费者模型 我们去超市商店等地购买商品时,我们大部分人都会说自己是消费者,而超市的各大供货商.工厂等,自然而然地也就成了我们的生产者.如此一来,生产者有了,消费者也有了,那么将二者联系起来的 ...

  6. 多道技术 进程 线程 协程 GIL锁 同步异步 高并发的解决方案 生产者消费者模型

    本文基本内容 多道技术 进程 线程 协程 并发 多线程 多进程 线程池 进程池 GIL锁 互斥锁 网络IO 同步 异步等 实现高并发的几种方式 协程:单线程实现并发 一 多道技术 产生背景 所有程序串 ...

  7. Java里的生产者-消费者模型(Producer and Consumer Pattern in Java)

    生产者-消费者模型是多线程问题里面的经典问题,也是面试的常见问题.有如下几个常见的实现方法: 1. wait()/notify() 2. lock & condition 3. Blockin ...

  8. Java多线程15:Queue、BlockingQueue以及利用BlockingQueue实现生产者/消费者模型

    Queue是什么 队列,是一种数据结构.除了优先级队列和LIFO队列外,队列都是以FIFO(先进先出)的方式对各个元素进行排序的.无论使用哪种排序方式,队列的头都是调用remove()或poll()移 ...

  9. Java多线程14:生产者/消费者模型

    什么是生产者/消费者模型 一种重要的模型,基于等待/通知机制.生产者/消费者模型描述的是有一块缓冲区作为仓库,生产者可将产品放入仓库,消费者可以从仓库中取出产品,生产者/消费者模型关注的是以下几个点: ...

随机推荐

  1. 点击按钮改变div背景色,再次点击恢复 -- 原生JS

    如果对您有帮助,记得点个赞哦!

  2. linux 信号与处理

    一.linux信号是什么 基本概念 信号是事件发生时对进程的通知机制,也就是所谓的软件中断.信号和硬件的中断类似,是软件层对中断机制的模拟,在多数情况下是无法预测信号产生的时间,所以软件层提供了一种处 ...

  3. 学习笔记——不带修序列莫队 (luogu2079)小B的询问

    莫队是一种对于询问的离线算法 时间复杂度:O(\(n \sqrt n\)) 大致思想就是 首先将询问离线,然后对原序列分块,使得每一个\(l和r\)都在一个块里 然后按照左节点排序,若所在的块相等,就 ...

  4. SpringBoot-使用异步

    SpringBoot提供了异步的支持,上手使用十分的简单,只需要开启一些注解支持,配置一些配置文件即可! 编写方法,假装正在处理数据,使用线程设置一些延时,模拟同步等待的情况: service: @S ...

  5. Flutter随笔(二)——使用Flutter Web + Docker + Nginx打造一个简单的Web项目

    前言 Flutter作为一个跨平台UI框架,功能十分强大,仅用一套代码便能编译出Android.iOS.Web.windows.macOS.Windows.Linux等平台上的应用,各平台应用体验高度 ...

  6. ClickHouse 存算分离架构探索

    背景 ClickHouse 作为开源 OLAP 引擎,因其出色的性能表现在大数据生态中得到了广泛的应用.区别于 Hadoop 生态组件通常依赖 HDFS 作为底层的数据存储,ClickHouse 使用 ...

  7. C语言中while 语句

    while的执行顺序 while 循环的执行顺序非常简单,它的格式是: while (表达式) { 语句: } 概念:当表达式为真,则执行下面的语句:语句执行完之后再判断表达式是否为真,如果为真,再次 ...

  8. SharkCTF2021 Classic_Crypto_king2

    crypto类题. 题面如下: 前面的代码给出了原理:后面的字符串第一行是print出的key,第二行是密文. 加密原理是,首先对table进行乱序处理,然后将明文flag按照(顺序table--&g ...

  9. 第一次Alpha Scrum Meeting

    本次会议为Alpha阶段第一次Scrum Meeting会议 会议概要 会议时间:2021年4月22日 会议地点:北航Inspiration Space咖啡厅 会议时长:1小时 会议内容简介:本次会议 ...

  10. [Beta]the Agiles Scrum Meeting 1

    会议时间:2020.5.9 21:00 1.每个人的工作 今天已完成的工作 讨论转会事项 经过组内成员的讨论,我们做出了非常艰难的决定:我们的组员老c将作为转会成员,离开我们的团队.感谢老c在Alph ...