概述
selectselect
selectswitchswitchselectcasecaseselect
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.println("quit")
return
}
}
}
selectc <- x<-quitcaseselectcasecase
结构
selectselectcasescase
type scase struct {
c *hchan
elem unsafe.pointer
kind uint16
pc uintptr
releasetime int64
}
defaultcasescaseccaseelemkindcase
const (
casenil = iota
caserecv
casesend
casedefault
)
case
现象
selectselectselectcase
非阻塞的收发
selectdefaultselect
func main() {
ch := make(chan int)
select {
case i := <-ch:
println(i)
default:
println("default")
}
}
$ go run main.go
default
default
随机执行
selectcaseselect
func main() {
ch := make(chan int)
go func() {
for range time.tick(1 * time.second) {
ch <- 0
}
}()
for {
select {
case <-ch:
println("case1")
case <-ch:
println("case2")
}
}
}
$ go run main.go
case1
case2
case1
case2
case2
case1
...
select<-chcase
编译
selectoselectoselectocaseocasedefault
上图展示的其实就是 select 在编译期间的结构,每一个 ocase 既包含了执行条件也包含了满足条件后执行的代码,我们在这一节中就会介绍 select 语句在编译期间进行的优化和转换。
编译器在中间代码生成期间会根据 select 中 case 的不同对控制语句进行优化,这一过程其实都发生在 walkselectcases 函数中,我们在这里会分四种情况分别介绍优化的过程和结果:
select 中不存在任何的 case;
select 中只存在一个 case;
select 中存在两个 case,其中一个 case 是 default 语句;
通用的 select 条件;
我们会按照这四种不同的情况拆分 walkselectcases 函数并分别介绍不同场景下优化的结果。
直接阻塞
selectcase
func walkselectcases(cases *nodes) []*node {
n := cases.len()
if n == 0 {
return []*node{mkcall("block", nil, nil)}
}
// ...
}
select {}block
func block() {
gopark(nil, nil, waitreasonselectnocases, traceevgostop, 1)
}
blockgoparkgoparkwaitreasonselectnocasesselect
独立情况
selectcaseselectifselect
select {
case v, ok <-ch:
// ...
}
if ch == nil {
block()
}
v, ok := <-ch
// ...
walkselectcasescasev, ok := <- ch
selectcasecasecaseselect
非阻塞操作
walkselectcasescaseselectcasedefault
发送
caseosendif/else
select {
case ch <- i:
// ...
default:
// ...
}
if selectnbsend(ch, i) {
// ...
} else {
// ...
}
selectnbsendchansendblock
func selectnbsend(c *hchan, elem unsafe.pointer) (selected bool) {
return chansend(c, elem, false, getcallerpc())
}
在这里我们只需要知道当前的发送过程不是阻塞的,哪怕是没有接收方、缓冲区空间不足导致失败了也会立即返回。
接收
由于从 channel 中接收数据可能会返回一个或者两个值,所以这里的情况会比发送时稍显复杂,不过改写的套路和逻辑确是差不多的:
select {
case v <- ch: // case v, received <- ch:
// ...
default:
// ...
}
if selectnbrecv(&v, ch) { // if selectnbrecv2(&v, &received, ch) {
// ...
} else {
// ...
}
selectnbrecvselectnbrecv2chanrecv
func selectnbrecv(elem unsafe.pointer, c *hchan) (selected bool) {
selected, _ = chanrecv(c, elem, false)
return
}
func selectnbrecv2(elem unsafe.pointer, received *bool, c *hchan) (selected bool) {
selected, *received = chanrecv(c, elem, false)
return
}
selectnbrecvselectnbrecv2chansendchanrecvblock
通用情况
select
casescaseselectgoscasescasecaseforifcase
caseselect
selv := [3]scase{}
order := [6]uint16
for i, cas := range cases {
c := scase{}
c.kind = ...
c.elem = ...
c.c = ...
}
chosen, revcok := selectgo(selv, order, 3)
if chosen == 0 {
// ...
break
}
if chosen == 1 {
// ...
break
}
if chosen == 2 {
// ...
break
}
selectscaseselectgocaseifcaseselectselectcase
运行时
我们已经充分地了解了 select 在编译期间的处理过程,接下来可以展开介绍 selectgo 函数的实现原理了。
func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) { } selectgo 是会在运行期间运行的函数,这个函数的主要作用就是从 select 控制结构中的多个 case 中选择一个需要执行的 case,随后的多个 if 条件语句就会根据 selectgo 的返回值执行相应的语句。
初始化
selectgocasepollorderlockorder
func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) {
cas1 := (*[1 << 16]scase)(unsafe.pointer(cas0))
order1 := (*[1 << 17]uint16)(unsafe.pointer(order0))
scases := cas1[:ncases:ncases]
pollorder := order1[:ncases:ncases]
lockorder := order1[ncases:][:ncases:ncases]
for i := range scases {
cas := &scases[i]
if cas.c == nil && cas.kind != casedefault {
*cas = scase{}
}
}
for i := 1; i < ncases; i++ {
j := fastrandn(uint32(i + 1))
pollorder[i] = pollorder[j]
pollorder[j] = uint16(i)
}
// sort the cases by hchan address to get the locking order.
// ...
sellock(scases, lockorder)
// ...
}
fastrandnselectlockordersellock
循环
selectselectcasesudogselectcase
casenilcase
caserecvcase
sendqrecvbufrecvrclose
casesendcase
rcloserecvqsend
casedefaultcasecaseselectgoselect
casesendqrecvq
func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) {
// ...
gp = getg()
nextp = &gp.waiting
for _, casei := range lockorder {
casi = int(casei)
cas = &scases[casi]
if cas.kind == casenil {
continue
}
c = cas.c
sg := acquiresudog()
sg.g = gp
sg.isselect = true
sg.elem = cas.elem
sg.c = c
*nextp = sg
nextp = &sg.waitlink
switch cas.kind {
case caserecv:
c.recvq.enqueue(sg)
case casesend:
c.sendq.enqueue(sg)
}
}
gp.param = nil
gopark(selparkcommit, nil, waitreasonselect, traceevgoblockselect, 1)
// ...
}
sudogsudoggopark
selectselectgosudog
func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) {
// ...
gp.selectdone = 0
sg = (*sudog)(gp.param)
gp.param = nil
casi = -1
cas = nil
sglist = gp.waiting
gp.waiting = nil
for _, casei := range lockorder {
k = &scases[casei]
if sg == sglist {
casi = int(casei)
cas = k
} else {
if k.kind == casesend {
c.sendq.dequeuesudog(sglist)
} else {
c.recvq.dequeuesudog(sglist)
}
}
sgnext = sglist.waitlink
sglist.waitlink = nil
releasesudog(sglist)
sglist = sgnext
}
c = cas.c
if cas.kind == caserecv {
recvok = true
}
selunlock(scases, lockorder)
goto retc
// ...
}
lockordercaseparamsudogcasesudogcasesudog
selectcasecasesudogsudogsudog
goto
bufrecv:
recvok = true
qp = chanbuf(c, c.recvx)
if cas.elem != nil {
typedmemmove(c.elemtype, cas.elem, qp)
}
typedmemclr(c.elemtype, qp)
c.recvx++
if c.recvx == c.dataqsiz {
c.recvx = 0
}
c.qcount--
selunlock(scases, lockorder)
goto retc
bufsend:
typedmemmove(c.elemtype, chanbuf(c, c.sendx), cas.elem)
c.sendx++
if c.sendx == c.dataqsiz {
c.sendx = 0
}
c.qcount++
selunlock(scases, lockorder)
goto retc
chansendchanrecvretc
sendrecv
recv:
recv(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2)
recvok = true
goto retc
send:
send(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2)
goto retc
panic
rclose:
selunlock(scases, lockorder)
recvok = false
if cas.elem != nil {
typedmemclr(c.elemtype, cas.elem)
}
goto retc
sclose:
selunlock(scases, lockorder)
panic(plainerror("send on closed channel"))
selectdefault
总结
selectselectselect
selectblock
selectcaseif ch == nil { block }; n;
case
selectcasedefaultselectnbrecvselectnbsend
selectgocaseifcase
selectselectgo
pollorderlockorder
pollordercase
casesudogsendqrecvqgopark
lockordercasesudog
selectselectgoselectgo
selectselectepollselect