通道的关闭过程发生了什么
关闭某个 channel,会执行函数 closechan:
// src/runtime/chan.go
func closechan(c *hchan) {
if c == nil { // 关闭一个 nil channel,panic
panic(plainError("close of nil channel"))
}
lock(&c.lock) // 上锁
if c.closed != 0 { // 如果 channel 已经关闭
unlock(&c.lock)
panic(plainError("close of closed channel"))
}
if raceenabled {
callerpc := getcallerpc()
racewritepc(c.raceaddr(), callerpc, funcPC(closechan))
racerelease(c.raceaddr())
}
c.closed = 1 // 修改关闭状态,指示 channel 已被关闭
var glist gList
// 将 channel 所有等待接收队列的里 sudog 释放
for {
sg := c.recvq.dequeue() // 从接收队列里出队一个 sudog
if sg == nil { // 出队完毕,跳出循环
break
}
// 如果 elem 不为空,说明此 receiver 未忽略接收数据
// 给它赋一个相应类型的零值
if sg.elem != nil {
typedmemclr(c.elemtype, sg.elem)
sg.elem = nil
}
if sg.releasetime != 0 {
sg.releasetime = cputicks()
}
// 取出 goroutine
gp := sg.g
gp.param = nil
if raceenabled {
raceacquireg(gp, c.raceaddr())
}
// 相连,形成链表
glist.push(gp)
}
// 将 channel 等待发送队列里的 sudog 释放
// 如果存在,这些 goroutine 将会 panic
for {
// 从发送队列里出队一个 sudog
sg := c.sendq.dequeue()
if sg == nil {
break
}
// 发送者会 panic
sg.elem = nil
if sg.releasetime != 0 {
sg.releasetime = cputicks()
}
gp := sg.g
gp.param = nil
if raceenabled {
raceacquireg(gp, c.raceaddr())
}
glist.push(gp) // 形成链表
}
unlock(&c.lock) // 解锁
// 遍历链表
for !glist.empty() {
gp := glist.pop() // 取最后一个
gp.schedlink = 0
goready(gp, 3) // 唤醒相应 goroutine
}
}
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
关闭 channel 的逻辑相对比较简单,对于一个 channel,recvq 和 sendq 中分别保存了阻塞的发送者和接收者。关闭 channel 后,对于等待接收者而言,会收到一个相应类型的零值;对于等待发送者,会直接 panic。所以,在不清楚 channel 还有没有接收者的情况下,不能贸然关闭它。
函数 closechan () 先上了一把大锁,接着把所有挂在这个 channel 上的 sender 和 receiver 全都连成一个 sudog 链表,再解锁。最后,再将所有的 sudog 全都唤醒。
唤醒之后,sender 会继续执行 chansend 函数里 goparkunlock 函数之后的代码,很不幸,检测到 channel 已经关闭了,发生 panic。而 receiver 则比较幸运,在进行一些扫尾工作后,函数返回。这里,selected 返回 true,而返回值 received 则要根据 channel 是否关闭,返回不同的值。如果 channel 关闭,received 的值为 false,否则为 true。
上次更新: 5/9/2023, 10:58:32 AM