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
