這天,小櫻在捕捉Golang牌時,Golang牌被嚇到,並且受傷,好巧不巧被一個田徑隊的學姐抓去收養。因為學姐跑太慢,所以好心的Golang牌決定開外掛讓學姐跑快一點,像這樣:
就好比演算法作業跑太慢的時候沒關係,多開幾個執行緒下去給他就好了(誤)
究竟學姐有沒有跑贏比賽呢?我才不會爆雷呢!要看自己去看
package main
import "fmt"
func main(){
ch1 := make(chan int)
ch2 := make(chan int)
go func(){
ch1 <- 1
}()
go func(){
ch2 <- 2
}()
select{
case <- ch1:
fmt.Println("接收ch1")
case <- ch2:
fmt.Println("接收ch2")
}
}
執行結果:
接收ch2
每次執行都不一樣,由作業系統決定
利用 select + case
可以同時監聽不同的 channel 與 switch 不一樣的地方在於 switch 是由上到下有順序的比對,而 select 是同時比對、隨機的
如果有多個 case 同時收到訊息,則 select 會隨機挑選 case:
package main
import "fmt"
func main(){
for i:=0; i<10; i=i+1{
ch := make(chan int)
go func(){
ch <- 1
}()
select{ // 三個 case 會同時收到 ch 上的訊息
case <- ch:
fmt.Println("1")
case <- ch:
fmt.Println("2")
case <- ch:
fmt.Println("3")
}
}
}
執行結果:
1
2
3
3
3
3
2
1
3
2
每次執行都不一樣,由作業系統決定
select 在實際使用上常常搭配計時器使用,當時間超過時就會結束監聽。另開一個通道做為計時器使用,時間一到就會發送訊息將 select 的等待打斷
package main
import(
"fmt"
"time"
)
func main(){
ch := make(chan int)
timeout := make(chan bool)
// 檢查有沒有超過 3 秒
go func(){
time.Sleep(3 * time.Second)
timeout <- true
}()
// 模擬一個需要耗時五秒的執行緒
go func(){
// 可自行調整秒數試試
time.Sleep(5 * time.Second)
ch <- 10
}()
select{
case num := <- ch:
fmt.Println(num)
case <- timeout:
fmt.Println("Time out !!")
}
}
執行結果:
Time out !!
在示範中,因為在收到 ch
訊息前就先收到 timeout
的訊息了,所以主執行緒離開 select,不會被擋住。這可以用來確保 select 最多只會給予 3 秒的等待。
圖片來源:庫洛魔法使第一季第卅五集
因為計時器常常使用,但使用時還要考慮計時的起點還有通道的使用等,還挺麻煩的,因此在 time package
中已經預設了一個函式,可以快速啟用計時器
func After(d Duration) <-chan Time
After waits for the duration to elapse and then sends the current time on the returned channel. It is equivalent to NewTimer(d).C. The underlying Timer is not recovered by the garbage collector until the timer fires. If efficiency is a concern, use NewTimer instead and call Timer.Stop if the timer is no longer needed.
package main
import(
"fmt"
"time"
)
func main(){
ch := make(chan int)
// 模擬一個需要耗時五秒的執行緒
go func(){
// 可自行調整秒數試試
time.Sleep(5 * time.Second)
ch <- 10
}()
select{
case num := <- ch:
fmt.Println(num)
case timer := <- time.After(3 * time.Second):
fmt.Println("Time out !!")
fmt.Println(timer)
}
}
執行結果
Time out !!
2020-09-19 18:17:13.878545989 +0000 UTC m=+3.000823994
執行結果第二行與作業系統當前時間有關
當 channel 沒有值傳送,select 會一直等待,此時變化造成 deadlock,如果有設置 default 則 select 不會做等待,注意!是「完全不會等待」
以下示範在沒有 default 的情況下,select 做無止盡的等待:
package main
import "fmt"
func main(){
ch := make(chan int)
select{
case <- ch:
fmt.Println("1")
}
}
執行結果:
fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan receive]:
main.main()
/mnt/588F-A4C6/go-tutorial/lesson19a.go:6 +0x56
exit status 2
使用 default:
package main
import "fmt"
func main(){
ch := make(chan int)
select{
case <- ch:
fmt.Println("1")
default:
fmt.Println("exit")
}
}
執行結果:
exit
default 不會針測是否 deadblock,而是沒在等的
如果有設置 ch 傳送訊息但比較慢一點點才收信那麼 select 會怎麼選擇呢? select 會直接選 default 因為有 default 在的情況下,select 不會等待其他 case
package main
import(
"fmt"
"time"
)
func main(){
ch := make(chan int)
go func(){
time.Sleep(1 * time.Second)
ch <- 1
}()
select{
case <- ch:
fmt.Println("1")
default:
fmt.Println("exit")
}
}
執行結果:
exit
換句話說在有設置 default 的情況下,select 不會做等待
注意以下情況
在有其他 case 的通道抵達的情況下,還會執行 defualt 內的動作嗎?
package main
import(
"fmt"
"time"
)
func main(){
case1 := 0
case2 := 0
defaultcase := 0
for i:=0; i < 10; i++{
ch1 := make(chan bool)
ch2 := make(chan bool)
go func(){
ch1 <- true
}()
go func(){
ch2 <- true
}()
// 稍微等待一下確定 ch1, ch2 訊息都抵達
time.Sleep(1 * time.Second)
select{
case <- ch1:
case1 = case1 + 1
case <- ch2:
case2 = case2 + 1
default:
defaultcase = defaultcase + 1
}
}
fmt.Println("case1", case1)
fmt.Println("case2", case2)
fmt.Println("default", defaultcase)
}
執行結果:
case1 5
case2 5
default 0
我試了幾次,case1, case2 幾乎都一半一半,不會跟 default 做競爭,基本上,在有其他 case 選擇的情況下,是不會選擇 default 的。只有在每個 case 都要等待時才會執行 default
剛剛所使用的特性都是 unbuffered channel 在等待接收的特性。除了等待接收,buffered channel 在 channel 無空位的情況下也會做等待。利用這個等待的特性,搭配 default 可以用來檢查 buffered channel 是否已滿
package main
import "fmt"
func main(){
buffer_channel := make(chan bool, 1)
// 占滿 buffer_channel
buffer_channel <- true
select{
case buffer_channel <- true: // 嘗試傳值給 buffer_channel
fmt.Println("buffer_channel 沒有滿")
default:
fmt.Println("buffer_channel 已滿")
}
}
執行結果:
buffer_channel 已滿
在使用 select 時:
本文大多數圖片來自: