- 原文地址:Part 24: Select
- 原文作者:Naveen R
- 譯者:咔嘰咔嘰 轉載請註明出處。
什麼是 select
select
語句用於從多個傳送/接收channel
中進行選擇的操作。 select
語句將阻塞直到其中一個傳送/接收操作準備就緒。如果有多個操作就緒,則隨機選擇其中一個操作。語法類似於switch
,只是每個case
語句被一個channel
操作取代了。讓我們深入研究一些程式碼,以便更好地理解
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
的實際用途。
讓我們假設我們有一個關鍵任務的應用,我們需要儘快將輸出返回給使用者。該應用程式的資料庫被複制並儲存在世界各地的不同伺服器中。假設函式server1
和server2
實際上與 2 個這樣的伺服器通訊。每個伺服器的響應時間取決於每個伺服器的負載和網路延遲。我們將請求傳送到兩個伺服器,然後使用select
語句在相應的channel
上等待響應。select
會選擇優先響應的伺服器,其他響應被忽略。這樣我們就可以向多個伺服器傳送相同的請求,並將最快的響應返回給使用者:)。
預設case
當其他case
都沒有準備就緒時,將會執行select
語句中的預設case
。這通常用於防止select
語句阻塞。
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")
}
}
}
複製程式碼
在上面的程式中,在第 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
複製程式碼