select是go語言中常用的一個關鍵字,其用法也一直被用作面試題來考核應聘者。今天,結合代碼來分析下select的主要用法。
首先,我們來從官方文檔看一下有關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 操作發生時,觸發相應的動作。
// communication: a send or receive operation om some channel
基本用法
select { //不停的在這里檢測 case <-chanl : //檢測有沒有數據可以讀 //如果chanl成功讀取到數據,則進行該case處理語句 case chan2 <- 1 : //檢測有沒有可以寫 //如果成功向chan2寫入數據,則進行該case處理語句 //假如沒有default,那么在以上兩個條件都不成立的情況下,就會在此阻塞//一般default會不寫在里面,select中的default子句總是可運行的,因為會很消耗CPU資源 default: //如果以上都沒有符合條件,那么則進行default處理流程 }
在一個select語句中,Go會按順序從頭到尾評估每一個發送和接收的語句。
如果其中的任意一個語句可以繼續執行(即沒有被阻塞),那么就從那些可以執行的語句中任意選擇一條來使用。
如果沒有任意一條語句可以執行(即所有的通道都被阻塞),那么有兩種可能的情況:
① 如果給出了default語句,那么就會執行default的流程,同時程序的執行會從select語句后的語句中恢復。
② 如果沒有default語句,那么select語句將被阻塞,直到至少有一個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.I f 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中的語句
文檔鏈接: https://golang.org/ref/spec#Select_statements
示例分析
示例1. 如果有一個或多個IO操作可以完成,則Go運行時系統會隨機的選擇一個執行; 否則的話,如果有default分支,則執行default分支語句,如果連default都沒有,則select語句會一直阻塞,直到至少有一個IO操作可以進行
package main import ( "fmt" "time" ) func main() { start := time.Now() c := make(chan interface{}) ch1 := make(chan int) ch2 := make(chan int) go func() { time.Sleep(4 * time.Second) close(c) }() go func() { time.Sleep(3 * time.Second) ch1 <- 3 }() go func() { time.Sleep(3 * time.Second) ch2 <- 5 }() fmt.Println("Blocking on read...") select { case <-c: fmt.Printf("Unblocked %v later.\n", time.Since(start)) case <-ch1: fmt.Printf("ch1 case...\n") case <-ch2: fmt.Printf("ch2 case...\n") default: fmt.Printf("default go...\n") } }
運行上述代碼,由於當前時間還未到3s。所以,目前程序會走default。
/* NEOdeMacBook-Pro:select_multiplexing_oct_20 zhenxink$ go run blog_select01.go Blocking on read... default go... NEOdeMacBook-Pro:select_multiplexing_oct_20 zhenxink$ */
修改上述代碼,將default注釋:
這時,select語句會阻塞,直到監測到一個可以執行的IO操作為止。這里,先會執行完睡眠3s的gorountine,此時兩個channel都滿足條件,這時系統會隨機選擇一個case繼續操作。
// 運行3 秒后: /* NEOdeMacBook-Pro:select_multiplexing_oct_20 zhenxink$ go run blog_select01.go Blocking on read... ch2 case... NEOdeMacBook-Pro:select_multiplexing_oct_20 zhenxink$ */
接着,繼續修改代碼,將ch1 和 ch2 的gorountine休眠時間改為5s:
go func() { time.Sleep(5*time.Second) ch1 <- 3 }()
go func() { time.Sleep(5*time.Second) ch2 <- 3 }()
此時會先執行到上面的gorountine,select執行的就是c的case。
/* NEOdeMacBook-Pro:select_multiplexing_oct_20 zhenxink$ go run blog_select01.go Blocking on read... Unblocked 4.00300947s later. NEOdeMacBook-Pro:select_multiplexing_oct_20 zhenxink$ */
1 package main 2 3 import "fmt" 4 5 var ch1 chan int 6 var ch2 chan int 7 var chs = []chan int{ch1, ch2} 8 var numbers = []int{1, 2, 3, 4, 5} 9 10 func main() { 11 12 select { 13 case getChan(0) <- getNumber(2): 14 fmt.Println("1st case is selected.") 15 case getChan(1) <- getNumber(3): 16 fmt.Println("2nd case is selected.") 17 default: 18 fmt.Println("default!.") 19 } 20 } 21 22 func getNumber(i int) int { 23 fmt.Printf("numbers[%d]\n", i) 24 return numbers[i] 25 } 26 27 func getChan(i int) chan int { 28 fmt.Printf("chs[%d]\n", i) 29 return chs[i] 30 }
此時,select語句走的是default操作。但是這時每個case的表達式都會被執行。以case1為例:
case getChan(0) <- getNumber(2): // ch1 <- 3 ,會 block住,因為無緩存的channel 是 synchronous 的,send 和 receive 必須同時滿足
系統會從左到右先執行getChan函數打印chs[0],然后執行getNumber函數打印numbers[2]。同樣,從上到下分別執行所有case的語句。所以,程序執行的結果為:
/* NEOdeMacBook-Pro:select_multiplexing_oct_20 zhenxink$ go run blog_select02.go chs[0] numbers[2] chs[1] numbers[3] default!. NEOdeMacBook-Pro:select_multiplexing_oct_20 zhenxink$ */
示例3. break關鍵字結束select
package main import "fmt" func main() { ch1 := make(chan int, 1) ch2 := make(chan int, 1) ch1 <- 3 ch2 <- 5 select { case <-ch1: fmt.Println("ch1 selected.") break fmt.Println("ch1 selected after break") case <-ch2: fmt.Println("ch2 selected.") fmt.Println("ch2 selected without break") } }
很明顯,ch1和ch2兩個通道都可以讀取到值,所以系統會隨機選擇一個case執行。我們發現選擇執行ch1的case時,由於有break關鍵字只執行了一句:
ch1 selected. // Process finished with exit code 0
但是,當系統選擇ch2的case時,打印結果為:
ch2 selected. ch2 selected without break // Process finished with exit code 0
如此就顯而易見,break關鍵字在select中的作用。
參考鏈接:
https://www.jianshu.com/p/2a1146dc42c3
https://www.cnblogs.com/wt645631686/p/9677868.html
