看了这几天的文章,感觉要每天一个大类了...这也是醉了,今天我们就来看看 Go 语言中的 channel
对了,百度了一下,发现 channel
没有一个很好的中文名字,虽然我也在 「 管道 」 和 「 通道 」 两个间徘徊,但这里,我还是翻译成 「 通道 」 吧
channel
是一个循环队列,可以同步或异步地传送某种类型的值,一个 channel
可以接收来自多个发送器的值,也可以将值发送给多个接收器
术语
channel
用到了许多有趣的概念,我们将这些概念罗列在此
- 「 Buffered 」( 可缓冲 ) - 内部缓冲区大小大于
1
的channel
- 「 Empty-buffered 」( 零缓冲 ) - 内部缓冲区大小为
0
的channel
- 「 Nil 」( 空值 ) -
channel
显式 ( 赋值 ) 或隐式 ( 未初始化的变量 ) 设置为nil
值 - 「 Unbuffered 」( 不缓冲 ) - 不含内部缓冲区的
channel
生命周期
一个 channel
是通过调用内建函数 make()
来初始化的。此调用会根据正在创建的通道类型接收一个或两个参数。第一个参数是必须的,它表示要创建的 channel
类型,第二个参数是可选的,表示要创建的 channel
缓冲区的大小。第二个通道仅在你需要创建 「 可缓冲 」 的 channel
时才需要
一个 channel
是通过调用内建函数 close()
来关闭的。关闭 channel
的动作是可选的。但它却是发送者告诉接收者该 channel
不再接收任何值了而已 ( 不再接收任何值,也就是不再会发送任何值了 )
声明
声明一个 「 空值 」 的 int
类型的 channel
var c chan int
初始化
初始化一个 「 不缓冲 」 的 channel
c = make( chan int )
初始化一个 「 可缓冲 」的 channel
,缓冲区大小为 2
c = make( chan int, 2 )
初始化一个 「 零缓冲 」 的 channel
,缓冲区大小为 0
c = make( chan int, 0 )
关闭或终止 channel
close(c)
陷阱
close(c)
这个动作,并非完美的
-
尝试关闭一个
nil
的channel
会引发一个异常 ( panic )package main func main() { var c chan int close(c) }
运行结果如下
$ go run demo.go panic: close of nil channel goroutine 1 [running]: main.main() /Users/yufei/go/demo.go:5 +0x2a exit status 2
-
尝试关闭一个已经关闭的
channel
也会引发一个异常 ( painic )package main func main() { var c chan int c = make( chan int , 0 ) close(c) close(c) }
运行结果如下
$ go run demo.go panic: close of closed channel goroutine 1 [running]: main.main() /Users/yufei/go/demo.go:7 +0x57 exit status 2
究其原因,它仅仅是一个通知作用而已,既然缺失了通知的对象,那自然就会报错了
channel 操作
用于与 channel
交互的操作符是 <-
。它用于向 channel
发送值或从中接收值,操作类型取决于 channel
变量的位置
接收操作
从一个 channel
中接收一个值时,变量在操作符的 右边
下面的代码尝试从 channel
变量 c
中接收一个值,并保存在 value
中
value := <- c
下面的代码尝试从 channel
变量 c
中接收一个值,并获取该 channel
是否关闭
value, ok := <- c
当变量 ok
的值为 true
时则表示已关闭,如果为 false
则表示未关闭
下面的代码尝试从 channel
变量 c
中循环的接收值,直到 c
被关闭
for value := range c { // 一些其它逻辑 }
发送操作
当要往一个 channel
中发送值时,变量在操作符的左边,要发送的值在操作符的右边
c <- value
陷阱
- 从一个已经关闭的通道里获取值,永远不会发生阻塞
- 从一个已经关闭的通道获取的值,该值永远是通道类型的空值,比如,如果是
chan int
,则值为0
,如果是chan string
,则值为""
最佳实战
如果你无法理解 <-
到底是用来接收还是用来发送,那么只要把它当作一个箭头就好了,这样就容易理解了
例如
<- c
是用于发送的,看这个操作,是不是很像让c
往箭头方向看c <-
是用于接收的,看这个操作,是不是很像箭头指向c