go中的select提供了一種方式來處理多個channels,select語句滿足如下條件
- 每個channel的值都會被計算
- 如果沒有channel有產出的話,會阻塞直到一個channel產出
- 如果多個channel有產出的話,會假隨機(pseudo-randomly)的選擇一個產出
- 在有default語句的情況下,沒有channel準備好的時候,會立即執行default的語句
下面有一個實例,源碼中對boring返回結果的註釋(第一行末尾的註釋)直譯是返回一個只接受string類型的channel。但是個人覺得翻譯爲返回的是一個只讀的string類型的channel更好,因爲後續的對c的操作,都只能從c中讀取string。
func boring(msg string) <-chan string { // Returns receive-only channel of strings.
c := make(chan string)
go func() { // We launch the goroutine from inside the function.
for i := 0; ; i++ {
c <- fmt.Sprintf("%s %d", msg, i)
time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
}
}()
return c // Return the channel to the caller.
}
func fanIn(input1, input2 <-chan string) <-chan string {
c := make(chan string)
go func() {
for {
select {
case s := <-input1: c <- s
case s := <-input2: c <- s
}
}
}()
return c
}
func main() {
joe := boring("Joe")
ann := boring("Ann")
c := fallin(joe, ann)
for i := 0; i < 20; i++ {
fmt.Println(<-c)
}
fmt.Println("You're both boring; I'm leaving.")
}
產生的結果如下,其中由於每個boring中的sleep的時間不確定,所以導致結果是Joe最終對應的i爲10,而Ann最終對應的i爲8
Joe 0
Ann 0
Joe 1
Ann 1
Joe 2
Ann 2
Joe 3
Ann 3
Joe 4
Ann 4
Joe 5
Ann 5
Joe 6
Ann 6
Ann 7
Joe 7
Joe 8
Joe 9
Ann 8
Joe 10
You're both boring; I'm leaving.
select語句滿足的條件1是值得思考的,每個channel都是需要計算取值的,因爲沒有正確的理解這句話是可能犯錯誤的。
可以思考下面這個函數的輸出,三秒之後函數會停止執行嗎?
func main() {
c := boring("Joe")
for {
select {
case s := <-c:
fmt.Println(s)
case <- time.After(3 * time.Second):
fmt.Println("Your're too slow")
return
}
}
}
答案是不會的,因爲每次執行到select語句中的各個選項的時候,都會計算每個case中的值。相當於在第一次輸出c的產出之後,第二次進入select的選擇的時候,time.After()就又重新開始計時了。要想在三秒之後停止這個函數,需要調整time.After的位置到select執行之前。
func main() {
c := boring("Joe")
timeout := time.After(3 * time.Second)
for {
select {
case s := <-c:
fmt.Println(s)
case <- timeout:
fmt.Println("Your're too slow")
return
}
}
}
本文內容翻譯自 Go Concurrency Patterns
做一個知識型討飯的,有些閒錢的,覺得對你有用的可以捐點