在Go語言中,select是一個強大的控制結構(類似於switch),可以用來處理多個channel的發送和接收操作。我們可以透過select同時監聽多個channel,並且在case符合時執行對應的操作。或是持續執行特定行為(前提是有default分支)
在select 開始前,會先依照case順序執行該case中的表達式,然後開始檢查每個case中的channel是否可以進行操作,如果有channel可以進行操作,則會執行該case中的表達式。如果有多個case可以執行,則會隨機選擇一個case來執行。如果沒有case可以執行,且沒有default分支,則select會阻塞,直到有case可以執行為止。
特性 | select | switch |
---|---|---|
主要用途 | 處理channel的發送和接收操作 | 基於值判斷 |
case | 只能針對channel判斷 | 可以是任意的值或表達式 |
同時滿足多個條件 | 隨機選擇一個可執行的channel操作 | 執行第一個匹配的 case,不會同時執行多個分支 |
是否會阻塞 | 可能阻塞,直到有channel操作可以進行(若無 default ) |
不會阻塞 |
default 分支 | 當沒有channel操作可執行時執行,可用於實現非阻塞的channel操作 | 當沒有匹配的 case 時執行 |
package main
import (
"fmt"
"time"
)
func routine1(c chan string, message int) {
time.Sleep(2 * time.Second)
c <- fmt.Sprintf("routine1: %d", message)
fmt.Println("routine1 done")
}
func routine2(c chan string, message int) {
time.Sleep(1 * time.Second)
c <- fmt.Sprintf("routine2: %d", message)
fmt.Println("routine2 done")
}
func main() {
c1 := make(chan string)
c2 := make(chan string)
after := time.After(10 * time.Second)
for i := 0; i < 3; i++ {
go routine1(c1, i)
go routine2(c2, i)
}
for {
select {
case msg1, ok := <-c1:
fmt.Println("Received from", msg1)
if !ok {
break
}
case msg2, ok := <-c2:
fmt.Println("Received from", msg2)
if !ok {
break
}
case <-after:
close(c1)
close(c2)
break
default:
fmt.Println("Waiting...")
time.Sleep(500 * time.Millisecond)
}
}
}
上面的code中,我們定義了兩個goroutine來模擬傳輸,並且在主goroutine中使用`select來同時監聽兩個channel。當其中一個channel有資料時,就會執行對應的操作。如果目前沒有channel可以接收資料,則會執行default分支,並且在after時間到達後關閉channel。
以上的code的輸出會是:
Waiting...
Received from routine2: 0
Received from routine2: 1
Received from routine1: 0
Waiting...
Received from routine2: 2
Received from routine1: 1
...
但如果你沒有指定default, 就會導致deadlock,這是因為select會一直等待channel有資料可以接收,而沒有任何channel可以接收資料時,select會一直等待且無法跳出。
package main
import (
"fmt"
"time"
)
func main(){
c1 := make(chan string)
c2 := make(chan string)
select {
case msg1 := <-c1:
fmt.Println("Received from", msg1)
case msg2 := <-c2:
fmt.Println("Received from", msg2)
}
}
我們也可以透過select檢查channel buffer是否已滿, 這樣可以避免在channel buffer已滿時導致的deadlock
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int, 1)
c <- 1
select {
case c <- 2:
fmt.Println("Sent 2")
default:
fmt.Println("Channel is full")
}
}
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int)
go func() {
time.Sleep(2 * time.Second)
c <- 1
close(c)
}()
LOOP:
for {
select {
case v, ok := <-c:
if !ok {
fmt.Println("Channel closed")
break LOOP
}
fmt.Println(v)
}
}
}
也可以透過time.After配合select實作Timeout機制。例如:
func timeout(f: func(), timeout time.Duration) {
done := make(chan bool)
go func() {
f()
done <- true
}()
LOOP:
select {
case <-done:
fmt.Println("Done")
case <-time.After(timeout):
fmt.Println("Timeout")
break LOOP
}
}
那麼今天的文章就到這告一段落,如果我的文章有任何地方有錯誤請在留言區反應
明天將會介紹Go語言的context包在非同步編程中的應用(如Sync, Timeout, Deadline等),敬請期待