為什麼需要select?
有時會遇到這種情景:需要對多個channel進行監聽。
如圖所示,就像一個人監聽多個通道一樣,假如採用for循環形式
for{
d1,ok:=<-c1
//...
d2,ok:=<-c2
//...
....
}
這個方式肯定走不通,原因是一旦某個通道監聽阻塞了,下面的部分都不會執行到。此外,即使使用ok狀態判斷不阻塞,但同時也會使for空轉,一下就將CPU拉升到90%,性能很差。
那麼有沒有一種方法,能像治安巡查一樣一輪輪的輪詢,而且能自動阻塞,就可以解決這個問題了。
go提供了select,正對應輪詢的思路,模式如下:
select{
case xx:
case xx:
case xx:
default:xx
}
運行過程
從上往下「巡查」,如果發現哪個case處於可執行狀態,就執行該條語句,那麼其餘語句就不執行了。
如果都不能執行,且有default語句時,就執行default
如果沒有default語句,那麼select整個就會阻塞(導致所在協程阻塞),直到解除。
但有人會有疑問,上面這些語句不是只能輪詢一次嗎?這個容易解決,我們給select外層再加一個for循環,這樣就可以無限的輪詢。
for{
select{
case xx:
case xx:
........
}
}
模式基本形成了。
客戶和服務端交互模擬
下面先看一個基礎的例子,主要用來模仿客戶和服務端的交互,模型如下:
ch := make(chan string)
//模擬啟動服務端
go func(ch chan string) {
for {
data := <-ch
fmt.Println("服務端接收到數據:", data)
time.Sleep(time.Second * 2)
//'roger'表示信息收到的意思..
ch <- "roger"
}
}(ch)
//模擬客戶端一次請求
ch <- "hello,服務端!"
select {
case ack := <-ch:
fmt.Println(ack)
case <-time.After(time.Second):
fmt.Println("返回超時...")
}
分析
- 服務端需要持續服務,因此採用for無限循環形式
- 客戶端的返回值就是和超時進行速度PK。
總結
介紹了select的基本原理,下篇說明選擇規則