Go Select使用
Go中的select和channel配合使用,通過select可以監聽多個channel的I/O讀寫事件,當 IO操作發生時,觸發相應的動作。
基本用法
//select基本用法 select { case <- chan1: // 如果chan1成功讀到數據,則進行該case處理語句 case chan2 <- 1: // 如果成功向chan2寫入數據,則進行該case處理語句 default: // 如果上面都沒有成功,則進入default處理流程
使用規則
1.如果沒有default分支,select會阻塞在多個channel上,對多個channel的讀/寫事件進行監控。 2.如果有一個或多個IO操作可以完成,則Go運行時系統會隨機的選擇一個執行,否則的話,如果有default分支,則執行default分支語句,如果連default都沒有,則select語句會一直阻塞,直到至少有一個IO操作可以進行。
快速返回
同時監聽不同的channel,做同一件工作,可以最快的返回結果。
package main import ( "fmt" "github.com/kirinlabs/HttpRequest" ) func main() { ch1 := make(chan int) ch2 := make(chan int) ch3 := make(chan int) go Getdata("https://www.baidu.com",ch1) go Getdata("https://www.baidu.com",ch2) go Getdata("https://www.baidu.com",ch3) select{ case v:=<- ch1: fmt.Println(v) case v:=<- ch2: fmt.Println(v) case v:=<- ch3: fmt.Println(v) } } func Getdata(url string,ch chan int){ req,err := HttpRequest.Get(url) if err != nil{ }else{ ch <- req.StatusCode() } }
隨機返回
同時監控不同的channel,配上default,select也不會阻塞。
package main import ( "fmt" "github.com/kirinlabs/HttpRequest" ) func main() { ch1 := make(chan int) ch2 := make(chan int) ch3 := make(chan int) go func(){ for { Getdata("https://www.baidu.com", ch1) Getdata("https://cn.bing.com", ch2) Getdata("https://cn.bing.com", ch3) } }() go func(){ for { select { case v := <-ch1: fmt.Println("信道1的結果:",v) case v := <-ch2: fmt.Println("信道2的結果:",v) case v := <-ch3: fmt.Println("信道3的結果:",v) default: continue } } }() select{} } func Getdata(url string,ch chan int){ req,err := HttpRequest.Get(url) if err != nil{ }else{ ch <- req.StatusCode() } }
通過select來檢測channel的關閉事件
func TestSelect1() { start := time.Now() c := make(chan interface{}) go func() { time.Sleep(2*time.Second) close(c) }() fmt.Println("Blocking on read...") select { case <-c: fmt.Printf("Unblocked %v later.\n", time.Since(start)) } }
注意:當close channel時,讀取channel的一方會從channel中讀取到value,false,此時的value一般情況下為nil。
該例子也可以用來通知當不使用channel時,關閉channel的情況。
通過channel通知,從而退出死循環
func TestExitLoop() { done := make(chan interface{}) go func() { time.Sleep(2*time.Second) close(done) }() workCounter := 0 loop: for { select { case <-done: break loop default: } // Simulate work workCounter++ time.Sleep(1*time.Second) } fmt.Printf("在通知退出循環時,執行了%d次.\n", workCounter) }
啟動一個goroutine,該goroutine在2s后,關閉channel。此時,主協程會在select中的case <-done分支中得到通知,跳出死循環。而在此之前,會執行default分支的代碼,這里是什么都不做。
超時機制
package main import ( "fmt" "time" ) func main() { ch := make(chan int) quit := make(chan bool) //新開一個協程 go func() { for { select { case num := <-ch: fmt.Println("num = ", num) case <-time.After(3 * time.Second): fmt.Println("超時") quit <- true } } }() for i := 0; i < 5; i++ { ch <- i time.Sleep(time.Second) } <-quit fmt.Println("程序結束") }
死鎖與默認情況
package main func main() { ch := make(chan string) select { case <-ch: } }
上面的程序中,我們在第 4 行創建了一個信道 ch
。我們在 select
內部(第 6 行),試圖讀取信道 ch。由於沒有 Go 協程向該信道寫入數據,因此 select
語句會一直阻塞,導致死鎖。該程序會觸發運行時 panic
,報錯信息如下:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
/tmp/sandbox416567824/main.go:6 +0x80
如果存在默認情況,就不會發生死鎖,因為在沒有其他 case 准備就緒時,會執行默認情況。我們用默認情況重寫后,程序如下:
package main import "fmt" func main() { ch := make(chan string) select { case <-ch: default: fmt.Println("default case executed") } }
以上程序會輸出:
default case executed
如果 select
只含有值為 nil
的信道,也同樣會執行默認情況。
package main import "fmt" func main() { var ch chan string select { case v := <-ch: fmt.Println("received value", v) default: fmt.Println("default case executed") } }
在線運行程序
在上面程序中,ch
等於 nil
,而我們試圖在 select
中讀取 ch
(第 8 行)。如果沒有默認情況,select
會一直阻塞,導致死鎖。由於我們在 select
內部加入了默認情況,程序會執行它,並輸出:
default case executed
空 select
package main func main() { select {} }
我們已經知道,除非有 case
執行,select
語句就會一直阻塞着。在這里,select
語句沒有任何 case
,因此它會一直阻塞,導致死鎖。該程序會觸發 panic
,輸出如下:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [select (no cases)]:
main.main()
/tmp/sandbox299546399/main.go:4 +0x20