『Golang』并发编程之通道(Channel)
通道(Channel)
通道是什么,为什么使用通道
「不要通过共享内存来通信,而应该通过通信来共享内存」
通道可以在多个 goroutine 之间传递数据
一个通道相当于一个先进先出(FIFO)的队列。也就是说,通道中的各个元素值都是严格地按照发送的顺序排列的,先被发送通道的元素值一定会先被接收。元素值的发送和接收都需要用到操作符 <-
。我们也可以叫它接送操作符。一个左尖括号紧接着一个减号形象地代表了元素值的传输方向
如何初始化通道
1 | func main() { |
- 使用
make
函数声明并初始化通道 - 通道的容量是
int
类型,但是不能小于 0 (缓冲通道和非缓冲通道)
通道的发送和接收操作都有哪些基本的特性
- 对于同一个通道,发送操作之间是互斥的,接收操作之间也是互斥的
- 发送操作和接收操作中,对元素值的处理都是不可分割的
- 发送操作在完全完成之前会被阻塞,接收操作也是如此
- 元素值从外界进入通道时会被复制
- 非缓冲通道的数据是直接从发送方复制到接收方
- 大多数情况下缓冲通道都会中转数据,如果发送时正好有人在等着接收,则会直接复制过去
什么时候会阻塞
正常情况下
- 缓冲通道:如果通道已满,所有的发送操作会被依次阻塞
- 非缓冲通道:无论是发送操作还是接收操作,一开始执行的时候都会被阻塞,直到配对的操作也开始执行
不正常情况
- 值为
nil
(没有使用make
初始化)时,两种操作都会被永久阻塞
什么时候会 panic
- 对关闭的通道发送数据
- 对关闭的通道再次关闭
关闭的通道有什么性质
关闭通道是指关闭通道的入口,通道关闭后仍可以从中取出数据
如果接收两个值,第二个值是 bool
,表示还能不能取出元素
如果值为 false
,表示通道已经关闭并且没有元素了,此时第一个元素会是零值
如果值为 true
,表示成功地取出了元素,注意你此时无法判断通道是否关闭
因此使用这个 bool
值判断通道是否关闭是有延迟的
永远在发送方关闭通道,不能在接收方关闭通道
什么是单向通道,有什么用
如果声明时包含接收操作符(<-
),就是单向通道
- 发送通道:
chan<-
,只能发不能收 - 接收通道:
<-chan
,只能收不能发
单向通道可以限制其他代码的行为
1 | func SendInt(ch chan<- int) { |
这里限制了函数内只能向通道内发送元素
1 | type Notifier interface { |
类似的,还有可以用在接口里
1 | func getIntChan() <-chan int { |
这里限制了得到返回值的程序,只能从通道中接收元素
怎么用
for range
从通道中取出元素
1 | intChan2 := getIntChan() |
性质与正常操作时相同
select
如何与通道连用
1 | select { |
select
语句只能与通道联用,是一种多路通信选择的控制结构,允许一个 goroutine 等待多个通信操作
它由若干个分支组成。每次执行这种语句的时候,只有一个分支中的接收/发送代码会被运行
select` 语句的分支选择规则有哪些
-
如果所有分支都阻塞,则会运行
defult
分支(也就是说含有默认分支的
select
永远不会阻塞) -
如果所有分支都阻塞,又没有
defult
分支,则会一直阻塞,直到有分支可以执行 -
如果同时有多个分支可以执行,则会随机选择一个
使用
select
的注意事项
-
如果通道关闭了,接收并不会阻塞,而同时获得零值与
false
所以如果发现通道关闭了,应当及时屏蔽对应的分支或者采取其他措施
(将通道赋值为
nil
可以屏蔽改对应的分支了,因为nil
的通道是一直阻塞的) -
select
语句只会将某分支的通道操作运行一次,所以如果你想连续操作的话,可以在for
中使用select
但是如果你在
select
中使用break
的话,只会跳出当前select
,而for
并不会跳出(如果想将
for
也跳出,可以在for
前面放一个label
,再将label
与break
一起使用) -
尽管
select
本身是并发安全的,但是不代表你的case
表达式和分支中的代码也是并发安全的