- select语句不使用default分支时,处于阻塞状态直到其中一个channel的收/发操作准备就绪(或者channel关闭或者缓冲区有值),如果同时有多个channel的收/发操作准备就绪(或者channel关闭)则随机选择其中一个。
- select语句使用default分支时,处于非阻塞状态,从所有准备就绪(或者channel关闭或者缓冲区有值)的channel中随机选择其中一个,如果没有则执行default分支。
严格来讲,select的实现包括两种顺序:一是:轮询顺序(poll order),二是加锁/解锁顺序(lock order)。个人理解,这道题的本意应该是在问轮询顺序。
轮询顺序的实现: 首先将所有case分支进行随机排序,然后按照这个随机顺序来遍历case分支,选择第一个符合条件(就绪或关闭或缓冲区有值)的channel即返回不再遍历后面的case分支。
runtime/select.go对应源码节选:
调用fastrandn来随机排序pollorder
// generate permuted order
for i := 1; i < ncases; i++ {
j := fastrandn(uint32(i + 1))
pollorder[i] = pollorder[j]
pollorder[j] = uint16(i)
}
loop:
// pass 1 - look for something already waiting
var dfli int
var dfl *scase
var casi int
var cas *scase
var recvOK bool
for i := 0; i < ncases; i++ {
casi = int(pollorder[i])
cas = &scases[casi]
c = cas.c
switch cas.kind {
case caseNil:
continue
case caseRecv:
sg = c.sendq.dequeue()
if sg != nil {
goto recv
}
if c.qcount > 0 {
goto bufrecv
}
if c.closed != 0 {
goto rclose
}
case caseSend:
if raceenabled {
racereadpc(c.raceaddr(), cas.pc, chansendpc)
}
if c.closed != 0 {
goto sclose
}
sg = c.recvq.dequeue()
if sg != nil {
goto send
}
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
}
不使用default(阻塞)验证如下:
select_block.go
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
var cnt [2]int
for i := 0; i < 1000; i++ {
go func(c1 chan int) {
c1 <- 0
}(ch1)
go func(c2 chan int) {
c2 <- 1
}(ch2)
//等10毫秒,确保两个channel都已准备就绪
time.Sleep(10 * time.Millisecond)
var index int
select {
case index = <-ch1:
cnt[index]++
case index = <-ch2:
cnt[index]++
}
}
fmt.Printf("cnt=%v\n", cnt)
}
连续执行多次,可以看到每次数字都不同,但比例接近于1 :1而且总和是1000,说明在同时接收到两个channel的返回时select等概率随机选择其中一个,而将另一个舍弃。
[root@dev tutorial]# go run select_block.go
cnt=[507 493]
[root@dev tutorial]# go run select_block.go
cnt=[496 504]
[root@dev tutorial]# go run select_block.go
cnt=[492 508]
[root@dev tutorial]# go run select_block.go
cnt=[493 507]
[root@dev tutorial]# go run select_block.go
cnt=[499 501]
[root@dev tutorial]# go run select_block.go
cnt=[505 495]
使用default分支(非阻塞)的验证:
select_nonblock.go
package main
import (
"fmt"
"time"
)
func main() {
var cnt [2]int
for i := 0; i < 1000; i++ {
timer1 := time.NewTimer(5 * time.Millisecond)
defer timer1.Stop()
timer2 := time.NewTimer(5 * time.Millisecond)
defer timer2.Stop()
//等10毫秒,确保两个channel都已准备就绪
time.Sleep(10 * time.Millisecond)
select {
case <-timer1.C:
cnt[0]++
case <-timer2.C:
cnt[1]++
default:
}
}
fmt.Printf("cnt=%v\n", cnt)
}
输出:
[root@dev tutorial]# go run select_nonblock.go
cnt=[498 502]
[root@dev tutorial]# go run select_nonblock.go
cnt=[544 456]
[root@dev tutorial]# go run select_nonblock.go
cnt=[520 480]
相关文章:
《Go语言:为什么要使用上下文(context)而不是计时器(timer)加通道(channel)的方式来控制协程》