channel(通道)
goroutine
和channel
是 Go 语言秉承的 CSP(Communicating Sequential Process)
并发模式的重要实现基础。该并发模式提倡通过通信共享内存而不是通过共享内存实现通信。
channel
可以实现在多个goroutine
之间进行通信,它是一种特殊的类型,遵循先入先出规则。每一个channel
只能传递指定元素类型的数据。
声明channel
channel
是引用类型,声明channel
的格式如下:
var 变量名 chan 元素类型
示例:
var ch chan bool // 声明一个传递布尔值的通道
声明得到的通道的值是nil
。
创建channel
通道使用make
函数初始化,其格式如下:
make(chan 元素类型, [缓冲大小])
示例:
ch1 := make(chan int) // 未设置缓冲大小,默认是0,所以ch1是无缓冲通道
channel操作
使用之前创建的ch1
。
- 发送:
ch1 <- 10
,把10发送到ch1中; - 接收:
<-ch1
,值可以用变量接收; - 关闭:
close(ch)
,只有在所有数据都发送完毕的时候才需要关闭通道,但关闭通道不是必须的,其可以被垃圾回收机制回收。
已关闭通道的特点
- 发送值会引发
panic
; - 可以接收值直到通道为空,通道为空时继续接收会得到对应类型的零值;
- 再次关闭该通道会引发
panic
。
无缓冲通道
无缓冲的通道就是缓冲大小为0,因为没有缓冲,所以无缓冲通道在发送值的同时必须有一方在接收值,也就是说,接收值和发送值的操作需要在两个 goroutine
中,否则程序就会因无限等待而死锁。
死锁示例:
func main() {
ch := make(chan int)
ch <- 10
fmt.Println("发送成功")
}
输出:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
e:/go/src/github.com/BattleL/studygo/day07/04channel/main.go:20 +0x5b
exit status 2
启用一个goroutine
解决死锁问题:
func main() {
ch := make(chan int)
go func() {
<-ch
}()
ch <- 10
fmt.Println("发送成功") // 发送成功
}
程序正常运行。
有缓冲通道
有缓冲通道,就是缓冲大小>0,如果设置为3,那么该通道就最多能存放3个元素,存满就会阻塞,直到有元素被接收。
示例:
func main() {
ch := make(chan int, 1)
ch <- 10
fmt.Println("发送成功") // 发送成功
}
内置的len
函数可以得到通道的元素数量,cap
函数可以得到通道的容量。
遍历通道
我们通常使用for range
遍历通道,但要注意通道必须在发送完毕后关闭,否则会陷入死锁。
遍历方式:
for i := range ch {
// i就是接收的值
}
判断通道关闭
通道接收时可以有两个返回值:
i, ok := <-ch
其中i
是接收值,ok
在接收成功时是true
,在通道关闭且通道为空时是false
。
可以利用ok
,结合for
循环实现通道遍历。
单向通道
当通道作为参数传递时,我们可能希望限制通道只能发送或只能接收,这就需要单向通道。
令out
参数只能发送,in
参数只能接收,函数名为test
,无返回值:
func test(out chan<- int, in <-chan int) {}
其中:
chan<- int
是int类型只写单向通道,只能发送不能接收;<-chan int
是int类型只读单向通道,只能接收不能发送。
注意:双向通道可以转换为单向通道,但反过来不行。
select多路复用
select
可以用于同时从多个通道随机接收数据,其类似于switch
语句,有一系列case
分支和一个默认的分支。每个case
可以对应一个通道的接收/发送操作。如果没有默认分支,select
会一直等待,直到能够完成某个case
的操作,如果多个分支都能完成,则随机选择一个。
示例:
func main() {
ch := make(chan int, 1)
for i := 0; i < 10; i++ {
select {
case x := <-ch:
fmt.Println(x)
case ch <- i:
}
}
}
输出:
0
2
4
6
8
如果将例子中的通道容量设为2,那么就会出现两个分支同时满足的情况,因为分支选择的随机性,输出将无法预测。
select
的用途:
- 提高代码可读性;
- 处理一个或多个通道的发送/接收操作;
- 多个分支同时满足时会随机选择(如果自己遍历判断,则规定了执行顺序);
- 没有分支的
select {}
会一直等待,可以用于阻塞main
函数。