- 原文地址:Part 24: Select
- 原文做者:Naveen R
- 譯者:咔嘰咔嘰 轉載請註明出處。
select
語句用於從多個發送/接收channel
中進行選擇的操做。 select
語句將阻塞直到其中一個發送/接收操做準備就緒。若是有多個操做就緒,則隨機選擇其中一個操做。語法相似於switch
,只是每一個case
語句被一個channel
操做取代了。讓咱們深刻研究一些代碼,以便更好地理解golang
package main
import (
"fmt"
"time"
)
func server1(ch chan string) {
time.Sleep(6 * time.Second)
ch <- "from server1"
}
func server2(ch chan string) {
time.Sleep(3 * time.Second)
ch <- "from server2"
}
func main() {
output1 := make(chan string)
output2 := make(chan string)
go server1(output1)
go server2(output2)
select {
case s1 := <-output1:
fmt.Println(s1)
case s2 := <-output2:
fmt.Println(s2)
}
}
複製代碼
在上面的程序中,在第 8 行server1
函數休眠 6 秒而後將文本從server1
寫入channel ch
。第 12 行server2
函數休眠 3 秒,而後從server2
寫入channel ch
。服務器
main
函數在 20 和 21 行分別調用server1
和server2
。網絡
在第 22 行,select
語句將阻塞直到其中一個case
準備就緒。在上面的程序中,server1
在 6 秒後寫入output1 channel
,而server2
在 3 秒後寫入output2 channel
。所以 select 語句將阻塞 3 秒並等待server2
寫入。 3 秒後,程序將打印,併發
from server2
複製代碼
而後終止。函數
select
的用途將上述程序中的函數命名爲server1
和server2
的緣由是爲了說明select
的實際用途。ui
讓咱們假設咱們有一個關鍵任務的應用,咱們須要儘快將輸出返回給用戶。該應用程序的數據庫被複制並存儲在世界各地的不一樣服務器中。假設函數server1
和server2
實際上與 2 個這樣的服務器通訊。每一個服務器的響應時間取決於每一個服務器的負載和網絡延遲。咱們將請求發送到兩個服務器,而後使用select
語句在相應的channel
上等待響應。select
會選擇優先響應的服務器,其餘響應被忽略。這樣咱們就能夠向多個服務器發送相同的請求,並將最快的響應返回給用戶:)。spa
case
當其餘case
都沒有準備就緒時,將會執行select
語句中的默認case
。這一般用於防止select
語句阻塞。code
package main
import (
"fmt"
"time"
)
func process(ch chan string) {
time.Sleep(10500 * time.Millisecond)
ch <- "process successful"
}
func main() {
ch := make(chan string)
go process(ch)
for {
time.Sleep(1000 * time.Millisecond)
select {
case v := <-ch:
fmt.Println("received value: ", v)
return
default:
fmt.Println("no value received")
}
}
}
複製代碼
Run in playgroundserver
在上面的程序中,在第 8 行process
函數休眠 10500 毫秒(10.5 秒),而後將process successful
寫入ch channel
。該函數在第 15 行被併發調用。
在併發調用process Goroutine
以後,main Goroutine
中啓動了無限循環。無限循環在每次迭代開始期間休眠 1000 毫秒(1 秒),並執行select
操做。在前 10500 毫秒期間,select
語句的第一種狀況即case v:= <-ch:
將不會準備就緒,由於process Goroutine
僅在 10500 毫秒後才寫入ch channel
。所以,在此期間將執行defualt
分支,程序將會打印 10 次no value received
。
在 10.5 秒以後,process Goroutine
將process successful
寫入ch
。 如今將執行select
語句的第一種狀況,程序將打印received value: process successful
而後程序終止。該程序將輸出,
no value received
no value received
no value received
no value received
no value received
no value received
no value received
no value received
no value received
no value received
received value: process successful
複製代碼
case
package main
func main() {
ch := make(chan string)
select {
case <-ch:
}
}
複製代碼
在上面的程序中,咱們在第一行建立了一個channel ch
。咱們嘗試從選擇的這個channel
讀取。而這個select
語句將一直阻塞,由於沒有其餘Goroutine
寫入此channel
,所以將致使死鎖。該程序將在運行時產生panic
同時打印,
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
/tmp/sandbox416567824/main.go:6 +0x80
複製代碼
若是存在默認case
,則不會發生此死鎖,由於在沒有其餘case
準備就緒時將執行默認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 channel
,也會執行默認case
。
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
中讀取。若是沒有默認case
,則select
將一直被阻塞並致使死鎖。因爲咱們在select
中有一個默認的case
,它將被執行而且程序將打印,
default case executed
複製代碼
select
的隨機性當select
語句中的多個case
準備就緒時,將會隨機挑選一個執行。
package main
import (
"fmt"
"time"
)
func server1(ch chan string) {
ch <- "from server1"
}
func server2(ch chan string) {
ch <- "from server2"
}
func main() {
output1 := make(chan string)
output2 := make(chan string)
go server1(output1)
go server2(output2)
time.Sleep(1 * time.Second)
select {
case s1 := <-output1:
fmt.Println(s1)
case s2 := <-output2:
fmt.Println(s2)
}
}
複製代碼
在上面的程序中,server1
和server2
協程在第 18 和 19 行分別被調用,而後main
協程休眠 1 秒。當運行到select
語句時,server1
已將from server1
寫入output1
,server2
已將from server2
寫入output2
,所以select
語句中的兩種狀況都準備就緒。若是屢次運行此程序,將會隨機輸出from server1
或from server2
。
select
package main
func main() {
select {}
}
複製代碼
你認爲上面的程序將會輸出什麼?
咱們知道select
語句將被阻塞,直到執行其中一個case
。在這種狀況下,select
語句沒有任何case
,所以它將一直阻塞致使死鎖。這個程序將會產生panic
,並輸出,
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [select (no cases)]:
main.main()
/tmp/sandbox299546399/main.go:4 +0x20
複製代碼