go channel使用规则

range可以在for循环中读取channel

  • 对于信道,其迭代值产生为在该信道上发送的连续值,直到该信道被关闭。若该信道为 nil,则range表达式将永远阻塞

空channel

  • 关闭一个空channel会导致当前goroutine引发panic
  • 向一个空channel发送值会导致当前的goroutine阻塞
  • 从一个空channel接收值也会导致当前的goroutine阻塞
  • 在空channel上的调用len和cap函数都统一返回零。

已关闭的Channel

  • 关闭一个已关闭的channel会引发panic
  • 向一个已关闭的channel发送值会引发panic。当这种send操作处于select块里面的case语句上时,它会随时导致select语句引发panic。
  • 从一个已关闭的channel上接收值既不会阻塞也不能panic,它一直能成功返回。只是返回的第二个值ok永远是false,表示接收到的v是在channel关闭之后拿到的,对应得值也是相应元素类型的零值。可以无限循环从已关闭的channel上接收值。

活跃的Channel

关闭操作

  • 从channel的接收协程队列中移除所有的goroutine,并唤醒它们。
  • 从channel的接收协程队列中移除所有的goroutine,并唤醒它们。
  • 一个已关闭的channel内部的缓冲数组可能不是空的,没有接收的这些值会导致channel对象永远不会被垃圾回收。

发送操作

  • 如果是阻塞型channel,那就从channel的接收协程队列中移出第一个协程,然后把发送的值直接递给这个协程。
  • 如果是阻塞型channel,并且channel的接收协程队列是空的,那么当前的协程将会阻塞,并进入到channel的发送协程队列里。
  • 如果是缓冲型channel,并且缓冲数组里还有空间,那么将发送的值添加到数组最后,当前协程不阻塞。
  • 如果是缓冲型channel,并且缓冲数组已经满了,那么当前的协程将会阻塞,并进入到channel的发送协程队列中。

接收操作

  • 如果是缓冲型channel,并且缓冲数组有值,那么当前的协程不会阻塞,直接从数组中拿出第一个值。如果发送队列非空,还需要将队列中的第一个goroutine唤醒。
  • 如果是阻塞型channel,并且发送队列非空的话,那么唤醒发送队列第一个协程,该协程会将发送的值直接递给接收的协程。
  • 如果是缓冲型channel,并且缓冲数组为空,或者是阻塞型channel,并且发送协程队列为空,那么当前协程将会阻塞,并加入到channel的接收协程队列中。

根据以上规则,我们可以得出以下结论

  • 如果channel关闭了,那么它的接收和发送协程队列必然空了,但是它的缓冲数组可能还没有空。
  • channel的接收协程队列和缓冲数组,同一个时间必然有一个是空的
  • channel的缓冲数组如果未满,那么它的发送协程队列必然是空的
  • 对于缓冲型channel,同一时间它的接收和发送协程队列,必然有一个是空的
  • 对于非缓冲型channel,一般来说同一时间它的接收和发送协程队列,也必然有一个是空的,但是有一个例外,那就是当它的发送操作和接收操作在同一个select块里出现的时候,两个队列都不是空的。

向缓冲型channel中写入数据然后关闭channel,还是可以读到channel中的数据,知道channel中的数据被读取完后ok为false。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main
import "fmt"
func main() {
var r chan int
r = make(chan int,5)

r <- 1
r <- 1
r <- 1
r <- 1
r <- 1
close(r)

v,ok := <- r
fmt.Println(v,ok) // output: 1 true
v,ok = <- r
fmt.Println(v,ok) // output: 1 true
v,ok = <- r
fmt.Println(v,ok) // output: 1 true
v,ok = <- r
fmt.Println(v,ok) // output: 1 true
v,ok = <- r
fmt.Println(v,ok) // output: 1 true
v,ok = <- r
fmt.Println(v,ok) // output: 0 false
}