select的作用
golang 中的 select 就是用来监听和 channel 有关的 IO 操作,当 IO 操作发生时,触发相应的动作。select 只能应用于 channel 的操作,既可以用于 channel 的数据接收,也可以用于 channel 的数据发送。如果 select 的多个分支都满足条件,则会随机的选取其中一个满足条件的分支执行。
基本用法
select { case <- chan1: // 如果 chan1 成功读到数据,则进行该 case 处理语句 case chan2 <- 1: // 如果成功向 chan2 写入数据,则进行该 case 处理语句 default: // 如果上面都没有成功,则进入default处理流程 }
select原理
select结构
在默认的情况下,select 语句会在编译阶段经过如下过程的处理:
- 将所有的
case
转换成包含Channel
以及类型等信息的 scase 结构体; - 调用运行时函数
selectgo
获取被选择的scase
结构体索引,如果当前的scase
是一个接收数据的操作,还会返回一个指示当前case
是否是接收的布尔值; - 通过
for
循环生成一组if
语句,在语句中判断自己是不是被选中的case
。
runtime\select.go
type scase struct { c *hchan // 当前case语句所操作的channel指针 elem unsafe.Pointer // data element数据元素 kind uint16 //表示该case的类型 pc uintptr // race pc (for race detector / msan) releasetime int64 }
//scase.kind values. const ( caseNil = iota caseRecv caseSend caseDefault )
scase.kind表示该case的类型,分别为:
- caseRecv:case语句中尝试读取scase.c中的数据,case <-Chan;
- caseSend:case语句中尝试向scase.c中写入数据,case Chan <- Send;
- caseDefault:default
执行select
在运行期间会调用selectgo()
函数,这个函数主要作用是从select
控制结构中的多个case
中选择一个需要执行的case
,随后的多个 if
条件语句就会根据 selectgo()
的返回值执行相应的语句。
运行时源码包src/runtime/select.go:selectgo()
定义了select选择case的函数:
// cas0为scase数组的首地址,selectgo()就是从这些scase中找出一个返回 // order0指向一个[2 * ncases] uint16类型的数组 // ncases表示scase数组的长度 //返回值: //int :选中case的编号 //bool:是否成功从channle中读取了数据 func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) { if debugSelect { print("select: cas0=", cas0, "\n") } cas1 := (*[1 << 16]scase)(unsafe.Pointer(cas0)) order1 := (*[1 << 17]uint16)(unsafe.Pointer(order0)) //将cas1从第一个元素开始切片,长度为ncases,容量为ncases //a[x:y:z] 切片长度: y-x 切片容量:z-x scases := cas1[:ncases:ncases] //所有case轮询顺序,占了前面ncase pollorder := order1[:ncases:ncases] //所有case语句中channel序列,占了后面ncase lockorder := order1[ncases:][:ncases:ncases] // 将涉及零个通道的发送/接收案例替换为caseNil,因此以下逻辑可以假定为非nil通道。 for i := range scases { cas := &scases[i] if cas.c == nil && cas.kind != caseDefault { *cas = scase{} } } var t0 int64 if blockprofilerate > 0 { t0 = cputicks() for i := 0; i < ncases; i++ { scases[i].releasetime = -1 } } // 生成排列顺序,pollorder重新排序 for i := 1; i < ncases; i++ { j := fastrandn(uint32(i + 1)) pollorder[i] = pollorder[j] pollorder[j] = uint16(i) } //通过Hchan地址进行排序以获得锁定顺序 //采用简单的堆排序,以确保n log n个时间和恒定的堆栈占用量 for i := 0; i < ncases; i++ { j := i // 替换相同channel 的case,以达到去重防止对channel加锁时重复加锁的目的 c := scases[pollorder[i]].c for j > 0 && scases[lockorder[(j-1)/2]].c.sortkey() < c.sortkey() { k := (j - 1) / 2 lockorder[j] = lockorder[k] j = k } lockorder[j] = pollorder[i] } for i := ncases - 1; i >= 0; i-- { o := lockorder[i] c := scases[o].c lockorder[i] = lockorder[0] j := 0 for { k := j*2 + 1 if k >= i { break } if k+1 < i && scases[lockorder[k]].c.sortkey() < scases[lockorder[k+1]].c.sortkey() { k++ } if c.sortkey() < scases[lockorder[k]].c.sortkey() { lockorder[j] = lockorder[k] j = k continue } break } lockorder[j] = o } if debugSelect { for i := 0; i+1 < ncases; i++ { if scases[lockorder[i]].c.sortkey() > scases[lockorder[i+1]].c.sortkey() { print("i=", i, " x=", lockorder[i], " y=", lockorder[i+1], "\n") throw("select: broken sort") } } } //锁住所有的channel sellock(scases, lockorder) var ( gp *g sg *sudog c *hchan k *scase sglist *sudog sgnext *sudog qp unsafe.Pointer nextp **sudog ) loop: // pass 1 - look for something already waiting // 按照随机顺序检测scase中的channel是否ready var dfli int var dfl *scase var casi int var cas *scase var recvOK bool //开始遍历case数组了 for i := 0; i < ncases; i++ { casi = int(pollorder[i]) cas = &scases[casi] c = cas.c switch cas.kind { case caseNil: continue // 接收chan case caseRecv: sg = c.sendq.dequeue() // 当chan的等待写队列不为空,需要等待 if sg != nil { goto recv } //当chan的缓存队列存在元素时,不需要等待 if c.qcount > 0 { goto bufrecv } // 当chan关闭时 if c.closed != 0 { goto rclose } //发送chan case caseSend: if raceenabled { racereadpc(c.raceaddr(), cas.pc, chansendpc) } // 当chan关闭时 if c.closed != 0 { goto sclose } //当chan的等待读消息的队列不为空,需要等待 sg = c.recvq.dequeue() if sg != nil { goto send } // chan的缓存队列的元素少于缓存容量时,还有位置,不需要等待 if c.qcount < c.dataqsiz { goto bufsend } case caseDefault: dfli = casi dfl = cas } } if dfl != nil { selunlock(scases, lockorder) casi = dfli cas = dfl goto retc } // pass 2 - enqueue on all chans //所有case都未ready,且没有default语句 //将当前协程加入到所有channel的等待队列 gp = getg() if gp.waiting != nil { throw("gp.waiting != nil") } 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 // No stack splits between assigning elem and enqueuing // sg on gp.waiting where copystack can find it. sg.elem = cas.elem sg.releasetime = 0 if t0 != 0 { sg.releasetime = -1 } sg.c = c // Construct waiting list in lock order. *nextp = sg nextp = &sg.waitlink switch cas.kind { case caseRecv: // 加入等待接收队列 c.recvq.enqueue(sg) case caseSend: // 加入等待发送队列 c.sendq.enqueue(sg) } } // wait for someone to wake us up //当将协程转入阻塞,等待被唤醒 gp.param = nil gopark(selparkcommit, nil, waitReasonSelect, traceEvGoBlockSelect, 1) sellock(scases, lockorder) gp.selectDone = 0 sg = (*sudog)(gp.param) gp.param = nil // pass 3 - dequeue from unsuccessful chans // otherwise they stack up on quiet channels // record the successful case, if any. // We singly-linked up the SudoGs in lock order. //唤醒后返回channel对应的case index casi = -1 cas = nil sglist = gp.waiting // Clear all elem before unlinking from gp.waiting. for sg1 := gp.waiting; sg1 != nil; sg1 = sg1.waitlink { sg1.isSelect = false sg1.elem = nil sg1.c = nil } gp.waiting = nil for _, casei := range lockorder { k = &scases[casei] if k.kind == caseNil { continue } if sglist.releasetime > 0 { k.releasetime = sglist.releasetime } if sg == sglist { // sg has already been dequeued by the G that woke us up. casi = int(casei) cas = k } else { c = k.c if k.kind == caseSend { c.sendq.dequeueSudoG(sglist) } else { c.recvq.dequeueSudoG(sglist) } } sgnext = sglist.waitlink sglist.waitlink = nil //释放所有的锁 releaseSudog(sglist) sglist = sgnext } //没找到case,重新循环 if cas == nil { goto loop } c = cas.c if debugSelect { print("wait-return: cas0=", cas0, " c=", c, " cas=", cas, " kind=", cas.kind, "\n") } if cas.kind == caseRecv { recvOK = true } if raceenabled { if cas.kind == caseRecv && cas.elem != nil { raceWriteObjectPC(c.elemtype, cas.elem, cas.pc, chanrecvpc) } else if cas.kind == caseSend { raceReadObjectPC(c.elemtype, cas.elem, cas.pc, chansendpc) } } if msanenabled { if cas.kind == caseRecv && cas.elem != nil { msanwrite(cas.elem, c.elemtype.size) } else if cas.kind == caseSend { msanread(cas.elem, c.elemtype.size) } } selunlock(scases, lockorder) goto retc bufrecv: // 可以从缓冲区接收 if raceenabled { if cas.elem != nil { raceWriteObjectPC(c.elemtype, cas.elem, cas.pc, chanrecvpc) } raceacquire(chanbuf(c, c.recvx)) racerelease(chanbuf(c, c.recvx)) } if msanenabled && cas.elem != nil { msanwrite(cas.elem, c.elemtype.size) } recvOK = true qp = chanbuf(c, c.recvx) if cas.elem != nil { // 将chan缓存中的数据拷贝到 case.elem。 eg: a := <-ch, a就是case.elem 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: //可以发送到缓冲区 if raceenabled { raceacquire(chanbuf(c, c.sendx)) racerelease(chanbuf(c, c.sendx)) raceReadObjectPC(c.elemtype, cas.elem, cas.pc, chansendpc) } if msanenabled { msanread(cas.elem, c.elemtype.size) } // 将cas.elem拷贝到chan的缓存中。eg: ch <- a, a 就是 cas.elem 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 recv: //可以从休眠的发件人(sg)接收 recv(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2) if debugSelect { print("syncrecv: cas0=", cas0, " c=", c, "\n") } recvOK = true goto retc rclose: //在封闭channel的末尾读取 selunlock(scases, lockorder) recvOK = false if cas.elem != nil { typedmemclr(c.elemtype, cas.elem) } if raceenabled { raceacquire(c.raceaddr()) } goto retc send: //可以发送到休眠的接收器(sg) if raceenabled { raceReadObjectPC(c.elemtype, cas.elem, cas.pc, chansendpc) } if msanenabled { msanread(cas.elem, c.elemtype.size) } send(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2) if debugSelect { print("syncsend: cas0=", cas0, " c=", c, "\n") } goto retc retc: if cas.releasetime > 0 { blockevent(cas.releasetime-t0, 1) } return casi, recvOK sclose: //在关闭的channel上发送 selunlock(scases, lockorder) panic(plainError("send on closed channel")) }
1.初始化pollorder切片和lockorder切片,其中pollorder是存放所有case的,lockorder是存放所有channel
2.对pollorder进行重排序,打乱所有case的顺序
3.锁定scase语句中所有的channel
4.按照随机顺序检测pollorder中case对应的channel是否ready:
4.1 如果case可读,则读取channel中数据,解锁所有的channel,然后返回(case index, true)
4.2 如果case可写,则将数据写入channel,解锁所有的channel,然后返回(case index, false)
4.3 所有case都未ready,则解锁所有的channel,然后返回(default index, false)
5.所有case都未ready,且没有default语句
5.1 将当前协程加入到所有channel的等待队列
5.2 当将协程转入阻塞,等待被唤醒
6.唤醒后返回channel对应的case index
6.1 如果是读操作,解锁所有的channel,然后返回(case index, true)
6.2 如果是写操作,解锁所有的channel,然后返回(case index, false)总结
select语句中除default外,各case执行顺序是随机的
select语句中如果没有default语句,则会阻塞等待任意一个case
select语句中读操作要判断是否成功读取,关闭的channel也可以读取
select语句中除default外,每个case只能操作一个channel,要么读要么写