深入解析:Go语言select

select是什么

   select是Go语言层面提供的一种多路复用机制,用于检测当前goroutine连接的多个channel是否有数据准备完毕,可用于读或写。

        Go语言的select语句,是用来起一个goroutine监听多个Channel的读写事件,提高从多个Channel获取信息的效率,相当于是单线程处理多个IO事件,其思想基本相同。

select的用法

select的基本使用模式如下:

select {
case <- channel1:     // 如果从channel1读取数据成功,执行case语句
do ...
case channel2 <- 1:   // 如果向channel2写入数据成功,执行case语句
do ...
default:              // 如果上面都没有成功,进入default处理流程
do ...
}

        可以看到,select的用法形式类似于switch,但是区别于switch的是select各个case的表达式必须都是channel的读写操作select通过多个case语句监听多个channel的读写操作是否准备好可以执行,其中任何一个case可以执行了则选择该case语句执行,如果没有可以执行的case,则执行default语句,如果没有default,则当前goroutine会阻塞。 

空select永久阻塞

当一个select中什么语句都没有,没有任何case,将会永久阻塞:

package main
func main() {
select {
}
}

运行结果:

fatal error: all goroutines are asleep - deadlock!

        程序因为select语句导致永久阻塞,当前goroutine阻塞之后,由于Go语言自带死锁检测机制,发现当前goroutine永远不会被唤醒,会报上述死锁错误。 

没有default且case无法执行的select永久阻塞

看下面示例:

package main
import (
"fmt"
)
func main() {
ch1 := make(chan int, 1)
ch2 := make(chan int, 1)
select {
case <-ch1:
fmt.Printf("received from ch1")
case num := <-ch2:
fmt.Printf("num is: %d", num)
}
}

运行结果:

fatal error: all goroutines are asleep - deadlock!

        程序中 select从两个channelch1ch2中读取数据,但是两个channel都没有数据,且没有goroutine往里面写数据,所以不可能读到数据,这两个case永远无法执行到,select也没有default,所以会出现永久阻塞,报死锁。 

单一case和default的select

package main
import (
"fmt"
)
func main() {
ch := make(chan int, 1)
select {
case <-ch:
fmt.Println("received from ch")
default:
fmt.Println("default!!!")
}
}

运行结果:

default!!!

        执行到select语句的时候,由于ch中没有数据,且没有goroutinechannel中写数据,所以case不可能执行到,就会执行default语句,打印出default!!!。 

 多个case和default的select

package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan int, 1)
ch2 := make(chan int, 1)
go func() {
time.Sleep(time.Second)
for i := 0; i < 3; i++ {
select {
case v := <-ch1:
fmt.Printf("Received from ch1, val = %d\n", v)
case v := <-ch2:
fmt.Printf("Received from ch2, val = %d\n", v)
default:
fmt.Println("default!!!")
}
time.Sleep(time.Second)
}
}()
ch1 <- 1
time.Sleep(time.Second)
ch2 <- 2
time.Sleep(4 * time.Second)
}

运行结果:

Received from ch1, val = 1
Received from ch2, val = 2
default!!!

        主goroutine中向后往管道ch1ch2中发送数据,在子goroutine中执行两个select,可以看到,在执行select的时候,那个case准备好了就会执行当下case的语句,最后没有数据可接受了,没有case可以执行,则执行default语句。

注意当多个case都准备好了的时候,会随机选择一个执行

package main
import (
"fmt"
)
func main() {
ch1 := make(chan int, 1)
ch2 := make(chan int, 1)
ch1 <- 5
ch2 <- 6
select {
case v := <-ch1:
fmt.Printf("Received from ch1, val = %d\n", v)
case v := <-ch2:
fmt.Printf("Received from ch2, val = %d\n", v)
default:
fmt.Println("default!!!")
}
}

运行结果:

Received from ch2, val = 6

多次执行,2个case都有可能打印,这就是select选择的随机性。

posted @ 2025-08-05 18:11  yjbjingcha  阅读(12)  评论(0)    收藏  举报