在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