官方文档有关select的描述
A “select” statement chooses which of a set of possible send or receive operations will proceed. It looks similar to a “switch” statement but with the cases all referring to communication operations. 一个select语句用来选择哪个case中的发送或接收操作可以被立即执行。它类似于switch语句,但是它的case涉及到channel有关的I/O操作。
select
就是用来监听和channel
有关的IO操作,当 IO 操作发生时,触发相应的动作,功能与epoll(nginx)/poll/select的功能类似。
select
是Go在语言层面提供的多路IO复用的机制,其可以检测多个channel是否ready(即是否可读或可写)
概述
- select语句中除default外,每个case操作必须是一个channel,要么读要么写
- 如果有多个case都可以运行,Go运行时系统会针对select随机公平地选出一个执行,其他不会执行
- select语句中如果没有default语句,且没有任意可运行case,则会一直阻塞等待任一case
分支选择执行规则
官方文档描述
Execution of a “select” statement proceeds in several steps:
1.For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send statements are evaluated exactly once, in source order, upon entering the “select” statement. The result is a set of channels to receive from or send to, and the corresponding values to send. Any side effects in that evaluation will occur irrespective of which (if any) communication operation is selected to proceed. Expressions on the left-hand side of a RecvStmt with a short variable declaration or assignment are not yet evaluated. 所有channel表达式都会被求值、所有被发送的表达式都会被求值。求值顺序:自上而下、从左到右. 结果是选择一个发送或接收的channel,无论选择哪一个case进行操作,表达式都会被执行。RecvStmt左侧短变量声明或赋值未被评估。
2.If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the “select” statement blocks until at least one of the communications can proceed. 如果有一个或多个IO操作可以完成,则Go运行时系统会随机的选择一个执行,否则的话,如果有default分支,则执行default分支语句,如果连default都没有,则select语句会一直阻塞,直到至少有一个IO操作可以进行.
3.Unless the selected case is the default case, the respective communication operation is executed. 除非所选择的情况是默认情况,否则执行相应的通信操作。
4.If the selected case is a RecvStmt with a short variable declaration or an assignment, the left-hand side expressions are evaluated and the received value (or values) are assigned. 如果所选case是具有短变量声明或赋值的RecvStmt,则评估左侧表达式并分配接收值(或多个值)。
5.The statement list of the selected case is executed. 执行所选case中的语句
- 在开始执行select语句的时候,所有跟在case关键字右边的发送语句或接受语句中的通道表达式和元素表达式都会先被求值。求值顺序是:自上而下、从左到右的。
- 其中的通道是可以为nil的,不管他是表达式还是标识符。但是这样的话,他所属的case就会永远被无视,就像case语句中不包含它一样。
- 在执行select语句的时候,运行系统自上而下地判断每个case中的发送或者接收操作是否可以被立即执行。这里的立即执行的意思是当前的Groutine不会因此操作而阻塞。
- 当发现第一个满足条件的case时,运行系统就会执行该case所包含的语句。这也意味着其他case被忽略。如果多个case满足条件,那么运行时系统就会通过一个伪随机的算法决定那个case将会被执行。
- 另一方面,如果被执行的select语句中的所有case都不满足选择的条件并且没有default case的话,那么当前Groutine就会一直被阻塞于此,直到某一个case 中的发送或接收操作可以被立即进行为止。(若这样的select语句中的所有case右边的通道都是nil,那么当前Groutine就会被永久地阻塞在这条语句上)
使用场景
-
timeout 机制(超时判断)
-
判断channel是否阻塞(或者说channel是否已经满了)
-
退出机制
import ( "fmt" "math/rand" "time" ) func generator() chan int { out := make(chan int) go func() { i := 0 for { time.Sleep( time.Duration(rand.Intn(1500)) * time.Millisecond) out <- i i++ } }() return out } func worker(id int, c chan int) { for n := range c { time.Sleep(time.Second) fmt.Printf("Worker %d received %d\n", id, n) } } func createWorker(id int) chan<- int { c := make(chan int) go worker(id, c) return c } func main() { var c1, c2 = generator(), generator() var worker = createWorker(0) var values []int tm := time.After(10 * time.Second) tick := time.Tick(time.Second) for { var activeWorker chan<- int var activeValue int if len(values) > 0 { activeWorker = worker activeValue = values[0] } select { case n := <-c1: values = append(values, n) case n := <-c2: values = append(values, n) case activeWorker <- activeValue: values = values[1:] case <-time.After(800 * time.Millisecond): fmt.Println("timeout") case <-tick: fmt.Println( "queue len =", len(values)) case <-tm: fmt.Println("bye") return } } }
超时判断:case <-time.After(800 * time.Millisecond)
退出
- return
- break + 具体的标记,或者goto也可以。否则其实不是真的退出
死锁
-
如果没有数据需要发送,select中又存在接收通道数据的语句,那么将发送死锁
import ( "fmt" ) func main() { var ch chan string // ch = nil select { case n := <-ch: fmt.Println("Received from ch:", n) } }
预防select死锁
-
可以通过加default
-
添加标识位
case e,ok:=<-ch: if !ok { //End break }else{ //continue }
-
-
空select,也会引起死锁
func main() { select {} }