Go 并发编程 - channel 小结
date
Nov 29, 2022
slug
go-channel
status
Published
tags
Go
summary
channel 使用注意事项
type
Post
简介
使用通信(channel)来共享内存,而不是通过共享内存来通信
channel 可以用于实现一个生产者消费者模型,用于生产者和消费者之间共享数据
日常使用中,我们可以用来做 goruntine 的同步,channel 使用简单效果可靠,下面来看看如何 channel 吧
初始化
使用 make 来初始化一个 channel,channel 支持多种类型元素
除了 channel 以外,make 还可以用来初始化 slice、map
channel 根据我们初始化的大小分为有缓冲 channel 和无缓冲 channel
有缓冲 channel
这种 channel,就相当于生产者消费者模型里面的缓冲队列,在这里我们初始化了大小为 3 的队列。
这种有缓冲队列的最大特点是异步,生产者和消费者之间不必同步执行。但是也会引起阻塞:
- 队列满时生产者阻塞
- 队列空时消费者阻塞
队列未满且存有一定数据的时候,生产者和消费者可以各司其职的完成存取。
举一个生活中的例子,有缓冲的 channel 相当于信箱,信箱未满的时候邮差直接把信件放到信箱里面就行了,而我们可以随时去检查信箱取出信件。(这里其实不太准确,日常生活中我们检查如果空信箱的话直接走掉了,但是程序中如果 channel 是空的话等待的 goroutine 是会被阻塞的,举这个例子只是为了突出异步的特点。)
无缓冲 channel
这种情况的消费者和生产者是同步的,同步指的是要求双方都准备好才能进行下一步。生产者生产数据后阻塞到数据被取走,而消费者获取数据之前也是阻塞在尝试获取的状态。下面这张图更加形象:
还是信箱的例子,无缓冲的 channel 相当于没有信箱了,快递员和收件人必须同时准备好才能结束发件和收件的动作。
开发中如果我们使用无缓冲 channel,务必要小心死锁的情况
这种情况运行后是会发生死锁的
fatal error: all goroutines are asleep - deadlock!
正确的写法是使用 goroutine 提前准备好接收:
另外需要注意的是,我们需要提前准备好接收,不然还是会死锁
channel 的使用
遍历
遍历有两种方式
推荐使用方式一,简单又清晰
select
搭配 select 是的我们在遍历 channel 的时候可以进行选择性操作,例如下面的例子:
上面的例子中,我们启动了一个 goruntine,读取 c 中前 10 个元素,然后我们调用 fibonacci,它执行了一个
for … select
的操作,第一个 select 不断往 c 中写入,第二个 select 从 quit 中读取,一旦从 quit 中获取到元素后,就退出循环而在外部,我们成功获取到了 fibonacci 数,可以看到 select 的使用和 Go 简单易用且强大的并发能力。
进阶:超时 channel
我们通过计时器和 select,可以完成定时任务,比如说上面的代码,我们调用After获取到一个
<-chan
,触发了相应的 case,终止了 select上面的思想可以扩展到在某时刻 or 某条件下做相应操作的功能
通道的限制
有的时候我们需要限制通道的读写,那么我们可以提前声明通道的类型,这种通道称为单向通道,特点是限制了写入或者读取的操作:
通道的关闭和异常
使用
close()
函数可以关闭一个通道,如果我们确认通道不再读写,那么记得及时关闭这里我们会好奇,对已经关闭的通道读写会有什么影响?这里也是面试会经常问到的,我们自己实验一下看看会有什么结果:
首先我们根据缓冲类型分为无缓冲 channel 和有缓冲 channel 实验:
无缓冲 channel
读会读取到零值,而写会 panic
有缓冲 channel
可以看到有缓冲 channel 的行为读取的时候,如果有预留值则读取预留值,没有则读取到零值,写同样会 panic
而我们再次
close
的话,不管有无缓冲,都会提示我们 panic: close of closed channel
现在我们可以总结了,从已经关闭的 channel 里面操作:
操作 / channel 类型 | 有缓冲 channel | 无缓冲 channel |
读 | 如果有预留值则返回,否则返回零值 | 返回零值 |
写 | panic: send on closed channel | panic: send on closed channel |
再次 close | panic: close of closed channel | panic: close of closed channel |
如果是 nil 的 channel 呢?
我们依次尝试读,写,close,发现都是 panic
那么我们可以进一步总结,读取已经关闭的 channel 操作结果:
操作 / channel 类型 | 有缓冲 channel | 无缓冲 channel | nil chahnel |
读 | 如果有预留值则返回,否则返回零值 | 返回零值 | 死锁 |
写 | panic: send on closed channel | panic: send on closed channel | 死锁 |
再次 close | panic: close of closed channel | panic: close of closed channel | panic: close of closed channel |
源码分析
深入理解Golang之channel - 掘金 (juejin.cn) 这篇文章分析得非常好,我先学习一下,后面再补充