概述
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