05 - 关闭一个 channel 的过程是怎样的
关闭某个 channel,会执行函数 closechan
1
func closechan(c *hchan) {
2
// 关闭一个 nil channel,panic
3
if c == nil {
4
panic(plainError("close of nil channel"))
5
}
6
7
// 上锁
8
lock(&c.lock)
9
// 如果 channel 已经关闭
10
if c.closed != 0 {
11
unlock(&c.lock)
12
// panic
13
panic(plainError("close of closed channel"))
14
}
15
16
// …………
17
18
// 修改关闭状态
19
c.closed = 1
20
21
var glist *g
22
23
// 将 channel 所有等待接收队列的里 sudog 释放
24
for {
25
// 从接收队列里出队一个 sudog
26
sg := c.recvq.dequeue()
27
// 出队完毕,跳出循环
28
if sg == nil {
29
break
30
}
31
32
// 如果 elem 不为空,说明此 receiver 未忽略接收数据
33
// 给它赋一个相应类型的零值
34
if sg.elem != nil {
35
typedmemclr(c.elemtype, sg.elem)
36
sg.elem = nil
37
}
38
if sg.releasetime != 0 {
39
sg.releasetime = cputicks()
40
}
41
// 取出 goroutine
42
gp := sg.g
43
gp.param = nil
44
if raceenabled {
45
raceacquireg(gp, unsafe.Pointer(c))
46
}
47
// 相连,形成链表
48
gp.schedlink.set(glist)
49
glist = gp
50
}
51
52
// 将 channel 等待发送队列里的 sudog 释放
53
// 如果存在,这些 goroutine 将会 panic
54
for {
55
// 从发送队列里出队一个 sudog
56
sg := c.sendq.dequeue()
57
if sg == nil {
58
break
59
}
60
61
// 发送者会 panic
62
sg.elem = nil
63
if sg.releasetime != 0 {
64
sg.releasetime = cputicks()
65
}
66
gp := sg.g
67
gp.param = nil
68
if raceenabled {
69
raceacquireg(gp, unsafe.Pointer(c))
70
}
71
// 形成链表
72
gp.schedlink.set(glist)
73
glist = gp
74
}
75
// 解锁
76
unlock(&c.lock)
77
78
// Ready all Gs now that we've dropped the channel lock.
79
// 遍历链表
80
for glist != nil {
81
// 取最后一个
82
gp := glist
83
// 向前走一步,下一个唤醒的 g
84
glist = glist.schedlink.ptr()
85
gp.schedlink = 0
86
// 唤醒相应 goroutine
87
goready(gp, 3)
88
}
89
}
Copied!
close 逻辑比较简单,对于一个 channel,recvq 和 sendq 中分别保存了阻塞的发送者和接收者。关闭 channel 后,对于等待接收者而言,会收到一个相应类型的零值。对于等待发送者,会直接 panic。所以,在不了解 channel 还有没有接收者的情况下,不能贸然关闭 channel。
close 函数先上一把大锁,接着把所有挂在这个 channel 上的 sender 和 receiver 全都连成一个 sudog 链表,再解锁。最后,再将所有的 sudog 全都唤醒。
唤醒之后,该干嘛干嘛。sender 会继续执行 chansend 函数里 goparkunlock 函数之后的代码,很不幸,检测到 channel 已经关闭了,panic。receiver 则比较幸运,进行一些扫尾工作后,返回。这里,selected 返回 true,而返回值 received 则要根据 channel 是否关闭,返回不同的值。如果 channel 关闭,received 为 false,否则为 true。这我们分析的这种情况下,received 返回 false。
Copy link