6. select
select
语句的语法:
- 每个
case
都必须是一个channel
的发送、或者接收操作。 - 所有
channel
表达式都会被求值。 - 如果任意
case
中表达式可以进行,它就执行,其他被忽略。 - 如果有多个
case
都可以运行,Select
会随机公平地选出一个执行。其他不会执行。 - 一个没有任何
case
的select
语句写作select{}
,会永远地等待下去 - 没有
break
。
否则: - 如果有
default
子句,则执行该语句。 - 如果没有
default
子句,select
将阻塞
,直到某个通信可以运行;Go 不会重新对 channel 或值进行求值。
6.1 与for 组合
for内部存在select时,break不能跳出for循环。需要用break到标签,或者goto到标签。
break 标签
for {
select {
case ...:
break ForEnd
}
fmt.Println("inside the for: ")
}
ForEnd:
goto 标签
for {
select {
case ...:
goto ForEnd
}
fmt.Println("inside the for: ")
}
ForEnd:
6.2 scase
src\runtime\select.go
// Select case descriptor.
// Known to compiler.
// Changes here must also be made in src/cmd/internal/gc/select.go's scasetype.
type scase struct {
c *hchan // chan
elem unsafe.Pointer // data element
kind uint16
pc uintptr // race pc (for race detector / msan)
releasetime int64
}
6.3 selectgo
select表达式的底层实现。
select表达式中的case、default被转换为scase数组。
src\runtime\select.go
// selectgo implements the select statement.
//
// cas0 points to an array of type [ncases]scase, and order0 points to
// an array of type [2*ncases]uint16. Both reside on the goroutine's
// stack (regardless of any escaping in selectgo).
//
// selectgo returns the index of the chosen scase, which matches the
// ordinal position of its respective select{recv,send,default} call.
// Also, if the chosen scase was a receive operation, it reports whether
// a value was received.
func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) {
6.4 selectgo执行逻辑
- 打乱所有case对应的scase结构体顺序。fastrandn
- 对所有channel加锁。
sellock(scases, lockorder)
- 遍历所有channel,判断是否可读,或者可写。
3.1 如果有case可读,读取channel中数据,解锁所有channel,返回对应scase,true。
recv(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2)
3.2 如果case可写,将数据写入channel,解锁所有channel,返回对应scase,false
3.3 没有case可读写,但是有default,解锁所有channel,返回default对应的scasefalse。 - 没有case可读写,没有default
4.1 将当前协程加入到channel等待队列。
4.2 将协程转入阻塞,等待被唤醒。
gopark(selparkcommit, nil, waitReasonSelect, traceEvGoBlockSelect, 1)
- 唤醒后
5.1 解锁所有channel
5.2 找到对应scase
5.3 如果是可读,解锁所有channel,返回对应scase,true
5.4 如果是可写,解锁所有channel,返回对应scase,false
pass 2 - enqueue on all chans
gp = getg()
// wait for someone to wake us up
解锁所有channel
sellock(scases, lockorder)
// pass 3 - dequeue from unsuccessful chans