Channel
Channel
==「不要通过共享内存来通信,而应该通过通信来共享内存」==
- 对于同一个通道,发送操作之间是互斥的,接收操作之间也是互斥的
- 发送操作和接收操作中,对元素值的处理是不可分割的
- 发送操作在完全完成之前会被阻塞,接受操作也是这样
- 元素值从外界进入通道时会被赋值
1 | func CalSquare() { |
1 | print: |
缓存
缓存通道
1
make(chan int , 10)
非缓存通道
1
make(chan int)
非缓存通道中只能接收一个值,这个值如果没有输出,那么直接会把整个通道堵塞,而缓存通道则是会在通道缓存满了再通道堵塞
堵塞的这个性质非常重要,可以实现一些意想不到的事情
为什么 ==done==一个非缓存通道 可以发送一个中断信号
因为
done
通道实际上是被用作一个信号量,而不是用来存储数据。在这种情况下,<- done
表达式用于从通道中接收消息,但由于通道是空的,所以实际上这里接收的是一个特殊的值,表示通道被关闭或者发送方已经完成了发送操作可以用于 超时操作
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41var w sync.WaitGroup
用 channel 模拟击球游戏
func init() {
rand.Seed(time.Now().UnixNano())
}
func main() {
court := make(chan int)
w.Wait()
go play("Nadal", court)
go play("Djokovic", court)
court <- 1
w.Wait()
}
func play(name string, court chan int) {
defer w.Done()
for {
ball, ok := <-court
if !ok {
println("Player", name, "won")
return
}
n := rand.Intn(100)
if n%13 == 0 {
println("Player", name, "missed")
close(court)
return
}
println("Player", name, "hit", ball)
ball++
court <- ball
}
}
单向通道
类型的声明包含接收符 ==<-chan==,就是单向通道
1 | // 这里的ch的参数就是个 只接收参数的单向通道 |
1 | // 这里的ch参数就是个 只输出参数的单向通道 |
什么时候会panic
对关闭的通道发送数据、
对关闭的通道再次关闭、
关闭后的通道的性质
关闭通道是指关闭通道的入口,通道关闭后仍可以从中取出数据
如果接收两个值,第二个值是 bool
,表示还能不能取出元素
如果值为 false
,表示通道已经关闭并且没有元素了,此时第一个元素会是零值
如果值为 true
,表示成功地取出了元素,注意你此时无法判断通道是否关闭
因此使用这个 bool
值判断通道是否关闭是有延迟的
永远在发送方关闭通道,不能在接收方关闭通道
可以从forrange中获取通道中的值
1 | for elem := range Chan { |
Select 和 Chan 连用
1 | select { |
select
语句只能与通道联用,是一种多路通信选择的控制结构,允许一个 goroutine 等待多个通信操作
它由若干个分支组成。每次执行这种语句的时候,只有一个分支中的接收/发送代码会被运行
如果所有分支都阻塞,则会运行
defult
分支(也就是说含有默认分支的
select
永远不会阻塞)如果所有分支都阻塞,又没有
defult
分支,则会一直阻塞,直到有分支可以执行如果同时有多个分支可以执行,则会随机选择一个
select 用于多个channel监听并收发消息,当任何一个case满足条件则会执行,若没有可执行的case,就会执行default,如果没有default,程序就会阻塞。
注意事项
如果通道关闭了,接收并不会阻塞,而同时获得零值与
false
所以如果发现通道关闭了,应当及时屏蔽对应的分支或者采取其他措施
(将通道赋值为
nil
可以屏蔽改对应的分支了,因为nil
的通道是一直阻塞的)select
语句只会将某分支的通道操作运行一次,所以如果你想连续操作的话,可以在for
中使用select
但是如果你在
select
中使用break
的话,只会跳出当前select
,而for
并不会跳出(如果想将
for
也跳出,可以在for
前面放一个label
,再将label
与break
一起使用)尽管
select
本身是并发安全的,但是不代表你的case
表达式和分支中的代码也是并发安全的
应用
定时器
1 | package main |
1 | # output : |
一次定时器
1 | package main |
1 | # output : |
超时控制
1 | package main |
1 | # output : |
如果想要实现超时,则把那个注释去掉
1 | # output : |
样例
1 | package main |
1 | # output : |