当前位置: 首页 > news >正文

Go语言---并发编程之channel(双channel,单channel)以及应用实例(生产者消费者、打印机模型)

Channel

goroutine 运行在相同的地址空间,因此访问共享内存必须做好同步。goroutine 通过通信来共享内存,而不是其享内存来通信。

引用类型 channel 是CSP 模式的具体实现,用于多个 goroutine 通讯。其内部实现了同步,确保并发安全。

channel类型

和 map 类似,channel 也一个对应 make 创建的底层数据结构的引用。
当我们复制一个 channel或用于函数参数传递时,我们只是拷贝了一个 channel 引用,因此调用者和被调用者将引用同一个channel 对象。和其它的引用类型一样,channel 的零值也是nil。
定义一个 channel 时,也需要定义发送到channel 的值的类型。channel 可以使用内置的 make()函数来创建:

make (chan Type)
make (chan Type,capacity)
  • 当capacity=0,channel是无缓冲阻塞读写的,当capacity>0时,channel有缓存,是非阻塞的,直到写满capacity个元素才阻塞写入。
  • chamnel 通过操作符<-来接收和发送数据,发送和接收数据语法:
channel<-value   //发送value 到channel
<-channel     //接收并将其丢弃
x:=<-channel   //从 channel 中接收数据,并賦偵给x
x,ok:=<-channel   //功能同上,同时检查通道是否已关闭或者是否为空

默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得goroutine 同步变的更加的简单,而不需要显式的lock。

实现

在这里插入图片描述

  • 管道放的位置一个在打印的前面,一个在打印的后面
  • 如果Person2先执行,管道里面没有数据,就会阻塞
  • Person1执行,打印完数据以后,往管道里面输入数据,Person2就会感知到,然后开始打印 。

通过channel实现同步和数据交互

在这里插入图片描述
在这里插入图片描述

无缓冲的 channel

无缓冲的通道(unbuffered channel)是指在接收前没有能力保存任何值的通道。
这种类型的通道要求发送 goroutine 和接收 goroutine 同时准备好,才能完成发送和接收操作。如果两个 goroutine 没有同时准备好,通道会导致先执行发送或接收操作的 goroutine 阻塞等待。
这种对通道进行发送和接收的交互行为本身就是同步的。其中任意一个操作都无法离开另个操作单独存在。
下图展示两个 goroutine 如何利用无缓冲的道来共享一个值:
在这里插入图片描述

  • 也就是channel本身不能存放东西,一个放另一个立马取。
  • 在第1步,两个 goroutine 都到达通道,但哪个都没有开始执行发送或者接收。
  • 在第 2 步,左侧的 goroutine 将它的手伸进了通道,这模拟了向通道发送数据的行为。这时,这个goroutine 会在通道中被锁住,直到交换完成。
  • 在第 3 步,右侧的 goroutine 将它的手放入通道,这模拟了从通道里接收数据。这个goroutine 一样也会在通道中被锁住,直倒交换完成。
  • 在第 4步和第 5步,进行交换,并最终,在第6步,两个 goroutine 都将它们的手从通道里拿出来,这模拟了被锁住的 goroutine 得到释放。两个 goroutine 现在都可以去做别的事情了。

建立无缓冲的channel

make (chan Type)//等价于make (chan Type,0)

如果没有指定缓冲区容量,那么该通道就是同步的,因此会阻塞到发送者准备好发送和接收者准备好援收。

在这里插入图片描述

  • ch<-i往channel中写数据,主协程感知到才会执行,这时候子协程也会阻塞等待,管道读取完数据以后,子协程才会继续。
  • 但是println打印数据,是在管道读取之后,所以打印的快慢是由系统决定的。

有缓冲的channel

有缓冲的通道(buffered channel)是一种在被接收前能存储一个或者多个值的通道。
这种类型的通道并不强制要求 goroutine 之问必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻寒。只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会阻寒。
这导致有缓冲的通道和无缓种的通道之间的一个很大的不同:无缓冲的通道保证进行发送和接收的 goroutine 会在同一时间进行数据交换:有缓冲的通道没有这种保证。
在这里插入图片描述

  • 在第 1步,右侧的 goroutine 正在从通道接收一个值。
  • 在第 2步,右侧的这个 goroutine 独立完成了接收值的动作,而左侧的 goroutine 正在发送一个新值到通道里。
  • 在第 3 步,左侧的 goroutine 还在向通道发送新值,而右侧的 goroutine 正在从通道接收另外一个值。这个步骤里的两个操作既不是同步的,也不会互相阻塞。
  • 最后,在第4步,所有的发送和接收都完成,而通道里还有几个值,也有一些空间可以存更多的值。

创建

make (chan Type,capacity)

如果给定了一个缓冲区容量,通道就是异步的。只要缓冲区有未使用空间用于发送数据,或还包含可以接收的数据,那么其通信就会无阻塞地进行。

实现

在这里插入图片描述

  • 如果存放够了三个,这时就会阻塞等待读取,当主协程读取过一个数据有空闲位置以后,子协程会继续执行,之后的打印顺序就不一定了。
    在这里插入图片描述

关闭channel

channel可以通过ok来检测channel是否还在打开状态,如果channel关闭,就不读数据。

在这里插入图片描述

  • num ,ok:=<-ch可以检测到通道是否关闭。

注意

  • channel不像文件一样需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束 range 循环之类的,才去关闭 channel;
  • 关闭 channel 后,无法向 channel 再发送数据(引发 panic 错误后导致接收立即返回零值):
  • 关闭 channel后,可以继续向chamnel 接收数据:
  • 对于 nil channel,无论收发都会被阻塞。
    在这里插入图片描述
  • 可以使用range来遍历channel,自动跳出循环
    在这里插入图片描述

单向channel

默认情况下,通道是双问的,也就是,既可以往里面发送数据也可以从里面接收数据。
但是,我们经常见一个通道作为参数进行传递而值希望对方是单向使用的,要么只让它发送数据,要么只让它接收数据,这时候我们可以指定通道的方向。单向 chamel 变量的声明非常简单,如下:

var ch1 chan int //ch1双向
var ch2 chan<-float64 //ch2单向,只能用于写float64数据
var ch3 <-chan int  //ch3单向,只能用于读int数据

chan<-表示数据进入管道,要把数据写进管道,对于调用者就是输出。
<-chan 表示数据从管道出来,对于调用者就是得到管道的数据,当然就是输入。
可以将 channel 隐式转换为单向队列,只收或只发,不能将单向channel转换为普通channel;

在这里插入图片描述

利用单通道,实现生产者消费者模型

在这里插入图片描述

相关文章:

  • Apache Spark分布式计算框架架构介绍
  • 从C向C++18——演讲比赛流程管理系统
  • Go语言入门之Map详解
  • 爬虫学习前记----Python
  • 辐射神经场算法——Instant-NGP / Mipi-NeRF 360 / 3D Gaussian Splatting
  • c语言数据结构--构造无向图(算法6.1),深度和广度遍历
  • day29--452. 用最少数量的箭引爆气球+435. 无重叠区间+763.划分字母区间
  • RABBITMQ的本地测试证书生成脚本
  • Windows右键没有新建Word、PPT与Excel的解决方法
  • vue + echart 饼形图
  • 每日刷题(二分图,二分查找,dfs搜索)
  • x.permute(0, 3, 1, 2).contiguous() 和 x.permute(0, 3, 1, 2)
  • C语言笔记29 •单链表经典算法OJ题-1.合并两个升序链表•
  • 在 PostgreSQL 里如何处理数据的归档和清理策略的优化?
  • Sentieon应用教程:本地使用-Quick_start
  • 「前端早读君006」移动开发必备:那些玩转H5的小技巧
  • 「译」Node.js Streams 基础
  • Android开源项目规范总结
  • - C#编程大幅提高OUTLOOK的邮件搜索能力!
  • CSS实用技巧干货
  • Cumulo 的 ClojureScript 模块已经成型
  • Docker下部署自己的LNMP工作环境
  • echarts花样作死的坑
  • Effective Java 笔记(一)
  • egg(89)--egg之redis的发布和订阅
  • exif信息对照
  • in typeof instanceof ===这些运算符有什么作用
  • Java教程_软件开发基础
  • MySQL数据库运维之数据恢复
  • php面试题 汇集2
  • Python3爬取英雄联盟英雄皮肤大图
  • 如何进阶一名有竞争力的程序员?
  • 如何设计一个微型分布式架构?
  • 深度学习在携程攻略社区的应用
  • 我与Jetbrains的这些年
  • media数据库操作,可以进行增删改查,实现回收站,隐私照片功能 SharedPreferences存储地址:
  • puppet连载22:define用法
  • RDS-Mysql 物理备份恢复到本地数据库上
  • 进程与线程(三)——进程/线程间通信
  • ​ 轻量应用服务器:亚马逊云科技打造全球领先的云计算解决方案
  • ​HTTP与HTTPS:网络通信的安全卫士
  • # 计算机视觉入门
  • #Linux(权限管理)
  • #Z0458. 树的中心2
  • #常见电池型号介绍 常见电池尺寸是多少【详解】
  • (33)STM32——485实验笔记
  • (C语言)fread与fwrite详解
  • (层次遍历)104. 二叉树的最大深度
  • (二)Kafka离线安装 - Zookeeper下载及安装
  • (附源码)springboot课程在线考试系统 毕业设计 655127
  • (附源码)springboot优课在线教学系统 毕业设计 081251
  • (附源码)ssm高校运动会管理系统 毕业设计 020419
  • (附源码)ssm考试题库管理系统 毕业设计 069043
  • (转)淘淘商城系列——使用Spring来管理Redis单机版和集群版
  • *(长期更新)软考网络工程师学习笔记——Section 22 无线局域网