概述

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