select的执行顺序
- select语句不使用default分支时,处于阻塞状态直到其中一个channel的收/发操作准备就绪(或者channel关闭或者缓冲区有值),如果同时有多个channel的收/发操作准备就绪(或者channel关闭)则随机选择其中一个。
- select语句使用default分支时,处于非阻塞状态,从所有准备就绪(或者channel关闭或者缓冲区有值)的channel中随机选择其中一个,如果没有则执行default分支。
严格来讲,select的实现包括两种顺序:一是:轮询顺序(poll order),二是加锁/解锁顺序(lock order)。个人理解,这道题的本意应该是在问轮询顺序。
轮询顺序的实现: 首先将所有case分支进行随机排序,然后按照这个随机顺序来遍历case分支,选择第一个符合条件(就绪或关闭或缓冲区有值)的channel即返回不再遍历后面的case分支。
select/switch中的break
在 for switch 和 for select 的 block中,如果用了break
break出的是switch 和 select ,并不是break for
解决方法:
1 用 return
2 用 break tag
package main
import "fmt"
func main() {
loop:
for {
switch {
case true:
fmt.Println("breaking out...")
break loop
}
}
fmt.Println("out!")
}
示例
不使用default
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)
}
Out:
cnt=[497 503]
可以看出总和为1000,而且比例接近1:1(随机)
使用default
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都已准备就绪,如果没有等待,则直接执行default分支,最后结果为[0 0]
time.Sleep(10 * time.Millisecond)
select {
case <-timer1.C:
cnt[0]++
case <-timer2.C:
cnt[1]++
default:
}
}
fmt.Printf("cnt=%v\n", cnt)
}
Out:
cnt=[512 488]
由于等待时间的存在,select还是在两个分支中进行随机选择,不走default流程;但是如果去掉等待时间,将变为
cnt=[0 0]
另一个例子
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 1, 1
for {
select {
case c<-x:
x, y=y,x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c:=make(chan int)
quit:=make(chan int)
go func() {
for i:=0;i<10;i++ {
fmt.Println(<-c)
}
quit<-0
}() // 无缓存channel通信必须要在不同的goroutine中,否则无法满足一个产生一个消费的同步原则
fibonacci(c,quit)
}
Out:
1
1
2
3
5
8
13
21
34
55
quit
超时退出机制
package main
import "time"
import . "fmt"
func main() {
c := make(chan int)
o := make(chan bool)
go func() {
for i:=0;i<100;i++ {
c<-i
}
}()
go func() {
for {
select {
case v := <- c:
Println(v)
case <- time.After(5 * time.Second):
Println("timeout")
o <- true
}
}
}()
<- o
}
该程序输出0-99,过5s后打印出timeout