Hi,你們好,我是明哥。html
在本身學習 Golang 的這段時間裏,我寫了詳細的學習筆記放在個人我的微信公衆號 《Go編程時光》,對於 Go 語言,我也算是個初學者,所以寫的東西應該會比較適合剛接觸的同窗,若是你也是剛學習 Go 語言,不防關注一下,一塊兒學習,一塊兒成長。git
個人在線博客:http://golang.iswbm.com
個人 Github:github.com/iswbm/GolangCodingTimegithub
前面寫過兩節關於 switch-case
的文章,分別是:golang
今天要學習一個跟 switch-case
很像,但還有點我的特點
的 select-case
,這一節本應該放在 學習 Go 協程:詳解信道/通道 裏一塊兒講的,可是當時漏了,直到有讀者給我提出,才注意到,今天就用這篇文章補充一下。微信
跟 switch-case 相比,select-case 用法比較單一,它僅能用於 信道/通道 的相關操做。函數
select { case 表達式1: <code> case 表達式2: <code> default: <code> }
接下來,咱們來看幾個例子幫助理解這個 select 的模型。學習
先建立兩個信道,並在 select 前往 c2 發送數據3d
package main import ( "fmt" ) func main() { c1 := make(chan string, 1) c2 := make(chan string, 1) c2 <- "hello" select { case msg1 := <-c1: fmt.Println("c1 received: ", msg1) case msg2 := <-c2: fmt.Println("c2 received: ", msg2) default: fmt.Println("No data received.") } }
在運行 select 時,會遍歷全部(若是有機會的話)的 case 表達式,只要有一個信道有接收到數據,那麼 select 就結束,因此輸出以下
c2 received: hello
select 在執行過程當中,必須命中其中的某一分支。
若是在遍歷完全部的 case 後,若沒有命中(命中
:也許這樣描述不太準確,我本意是想說能夠執行信道的操做語句)任何一個 case 表達式,就會進入 default 裏的代碼分支。
但若是你沒有寫 default 分支,select 就會阻塞,直到有某個 case 能夠命中,而若是一直沒有命中,select 就會拋出 deadlock
的錯誤,就像下面這樣子。
package main import ( "fmt" ) func main() { c1 := make(chan string, 1) c2 := make(chan string, 1) // c2 <- "hello" select { case msg1 := <-c1: fmt.Println("c1 received: ", msg1) case msg2 := <-c2: fmt.Println("c2 received: ", msg2) // default: // fmt.Println("No data received.") } }
運行後輸出以下
fatal error: all goroutines are asleep - deadlock! goroutine 1 [select]: main.main() /Users/MING/GolandProjects/golang-test/main.go:13 +0x10f exit status 2
解決這個問題的方法有兩種
一個是,養成好習慣,在 select 的時候,也寫好 default 分支代碼,儘管你 default 下沒有寫任何代碼。
package main import ( "fmt" ) func main() { c1 := make(chan string, 1) c2 := make(chan string, 1) // c2 <- "hello" select { case msg1 := <-c1: fmt.Println("c1 received: ", msg1) case msg2 := <-c2: fmt.Println("c2 received: ", msg2) default: } }
另外一個是,讓其中某一個信道能夠接收到數據
package main import ( "fmt" "time" ) func main() { c1 := make(chan string, 1) c2 := make(chan string, 1) // 開啓一個協程,能夠發送數據到信道 go func() { time.Sleep(time.Second * 1) c2 <- "hello" }() select { case msg1 := <-c1: fmt.Println("c1 received: ", msg1) case msg2 := <-c2: fmt.Println("c2 received: ", msg2) } }
以前學過 switch 的時候,知道了 switch 裏的 case 是順序執行的,但在 select 裏卻不是。
經過下面這個例子的執行結果就能夠看出
當 case 裏的信道始終沒有接收到數據時,並且也沒有 default 語句時,select 總體就會阻塞,可是有時咱們並不但願 select 一直阻塞下去,這時候就能夠手動設置一個超時時間。
package main import ( "fmt" "time" ) func makeTimeout(ch chan bool, t int) { time.Sleep(time.Second * time.Duration(t)) ch <- true } func main() { c1 := make(chan string, 1) c2 := make(chan string, 1) timeout := make(chan bool, 1) go makeTimeout(timeout, 2) select { case msg1 := <-c1: fmt.Println("c1 received: ", msg1) case msg2 := <-c2: fmt.Println("c2 received: ", msg2) case <-timeout: fmt.Println("Timeout, exit.") } }
輸出以下
Timeout, exit.
上面例子裏的 case,好像都只從信道中讀取數據,但實際上,select 裏的 case 表達式只要求你是對信道的操做便可,無論你是往信道寫入數據,仍是從信道讀出數據。
package main import ( "fmt" ) func main() { c1 := make(chan int, 2) c1 <- 2 select { case c1 <- 4: fmt.Println("c1 received: ", <-c1) fmt.Println("c1 received: ", <-c1) default: fmt.Println("channel blocking") } }
輸出以下
c1 received: 2 c1 received: 4
select 與 switch 原理很類似,但它的使用場景更特殊,學習了本篇文章,你須要知道以下幾點區別:
系列導讀
24. 超詳細解讀 Go Modules 前世此生及入門使用