在go的語言規範中,select中的case的執行順序是隨機的,當有多個case都可以運行時,select會隨機公平地選出一個執行,其它的便不會執行
package main
import "fmt"
func main() {
ch := make (chan int, 1)
ch<-1
select {
case <-ch:
fmt.Println("隨機一")
case <-ch:
fmt.Println("隨機二n")
}
}
輸出內容爲隨機一二里面的任意一個。case後面必須是channel操作,否則報錯;default子句總是可運行的,所以沒有default的select纔會阻塞等待事件;沒有運行的case,那麼將會阻塞事件發生報錯(死鎖)
二、select的應用場景
1.timeout超時判斷
package main
import (
"fmt"
"time"
)
func main() {
timeout := make (chan bool, 1)
go func() {
time.Sleep(1*time.Second) // 休眠1s,如果超過1s還沒I操作則認爲超時,通知select已經超時啦~
timeout <- true
}()
ch := make (chan int)
select {
case <- ch:
case <- timeout:
fmt.Println("超時啦!")
}
}
也可以這麼寫
package main
import (
"fmt"
"time"
)
func main() {
timeout := make (chan bool, 1)
go func() {
time.Sleep(1*time.Second) // 休眠1s,如果超過1s還沒I操作則認爲超時,通知select已經超時啦~
timeout <- true
}()
ch := make (chan int)
select {
case <- ch:
case <- timeout:
fmt.Println("超時啦!")
}
}
2.判斷channel是否阻塞(或者說channel是否已經滿了)
package main
import (
"fmt"
)
func main() {
ch := make (chan int, 1) // 注意這裏給的容量是1
ch <- 1
select {
case ch <- 2:
default:
fmt.Println("通道channel已經滿啦,塞不下東西了!")
}
}
3.退出機制
package main
import (
"fmt"
"time"
)
func main() {
i := 0
ch := make(chan string, 0)
defer func() {
close(ch)
}()
go func() {
DONE:
for {
time.Sleep(1*time.Second)
fmt.Println(time.Now().Unix())
i++
select {
case m := <-ch:
println(m)
break DONE // 跳出 select 和 for 循環
default:
}
}
}()
time.Sleep(time.Second * 4)
ch<-"stop"
}
三、select的實現
select-case的channel操作編譯成了if-else,如:
select{
case v := <-c:
...foo
default:
...bar
}
會被編譯爲
if selectnbrecv(&v, c) {
...foo
} else {
..bar
}
類似地
select {
case v, ok := <-c:
...foo
default:
...bar
}
會被編譯爲
if c!=nil && selectnbrecv2(&v, &ok, c) {
...foo
} else {
...bar
}
。、、、、、、、、
、、、、、
三、select死鎖
select不注意也會導致goruntine掛起
func main() {
ch := make(chan string)
select {
case <-ch:
}
}
package main
func main() {
select{}
}
上面兩種情況都會導致goruntine掛起,不過因爲是在main函數裏而且只有一個goroutine,所以會直接panic