Go 语言 select 语句
1、概述
- select 是 Go 中的一个控制结构,类似于 switch 语句;
- select 语句只能用于通道操作,每个 case 必须是一个通道操作,要么是发送要么是接收;
- select 语句会监听所有指定的通道上的操作:
- 一旦其中一个通道准备好就会执行相应的代码块;
- 如果多个通道都准备好,那么 select 语句会随机选择一个通道执行;
- 如果所有通道都没有准备好,那么执行 default 块中的代码;
- 如果所有通道都没有准备好,并且没有 default 块,那么 select 语句将会阻塞,直到至少一个通道准备好为止;(这意味着程序会一直等待,直到有一个通道可以进行操作)
2、语法
select {
case <- channel1:
// 执行的代码
case value := <- channel2:
// 执行的代码
case channel3 <- value:
// 执行的代码
// 你可以定义任意数量的 case
default:
// 所有通道都没有准备好,执行的代码
}
- 每个 case 都必须是一个通道;
- 所有 channel 表达式都会被求值;
- 所有被发送的表达式都会被求值;
- 如果任意摸个通道可以运行,它就执行,其他被忽略;
- 如果有多个 case 都可以运行,select 会随机公平地选出一个执行,其他不会执行;
- 否则,如果有 default 子句,则执行该语句;
- 如果没有 default 子句,select 将阻塞,直到某个通道可以运行;
- Go不会重新对 channel 或值进行求值;
3、简单实例
- 没有 default 的 select;
func SimpleDemoNoDefault() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
c1 <- "channel one"
}()
go func() {
time.Sleep(1 * time.Second)
c2 <- "channel two"
}()
for i := 0; i < 3; i++ {
select {
case msg1 := <-c1:
fmt.Println("received", msg1)
case msg2 := <-c2:
fmt.Println("received", msg2)
}
}
}
- 有 default 的 select:
func SimpleDemoWithDefault() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
for {
c1 <- "from 1"
}
}()
go func() {
for {
c1 <- "from 2"
}
}()
for {
time.Sleep(1 * time.Second)
select {
case msg1 := <-c1:
fmt.Println(msg1)
case msg2 := <-c2:
fmt.Println(msg2)
default:
fmt.Println("no message received")
}
}
}
4、实现原理
通过 select 语句,可以实现主线程和其他线程之间的互动;
实现原理
详情参考:
https://www.jb51.net/article/259610.htm
GoLang
实现select
时,定义了一个数据结构表示每个case
语句(包含default
,default
实际上是一种特殊的case
);select
执行过程可以看成一个函数,函数输入case
数组,输出选中的case
,然后程序流程转到选中的case
块;
执行流程
- 在默认情况下,select 在编译阶段经过如下过程的处理:
- 将所有 case 转换成包含 channel 以及类型等信息的
scase
结构体; - 编译器调用运行时函数 selectgo 来执行 select 语句,这个函数会根据 scase 结构体数组中的各个 case 条件,选择其中一个 case 来执行,并返回被选择 scase 结构体的索引;
- 如果当前 scase 是一个接收操作,函数会返回一个布尔值,表示接收操作是否成功;
- 编译器会根据 scase 结构体数组中的各个 case 条件,生成一组 if 语句,每个 if 语句会判断当前的 case 是否是被选择的 case,如果是,则执行相应的操作;
- 将所有 case 转换成包含 channel 以及类型等信息的
scase 数据结构
源码包 src/runtime/select.go
type scase struct {
c *hchan // chan
elem unsafe.Pointer // data element
}
c
:表示通道,存储 case 所关联的通道,不包含 default;这也说明了一个case 语句只能操作一个 channel;elem
:表示数据类型,用于存储 case 所关联的数据元素;
判断某个 scase 属于什么操作
select {
case x := <-ch:
fmt.Println("接收到数据:", x)
case ch <- 10:
fmt.Println("发送数据成功")
default:
fmt.Println("没有进行任何操作")
}
<-ch
:表示接收操作,将通道 ch 中的数据赋值给变量 x;ch <- 10
:表示发送操作,将数据 10 发送到通道 ch 中;default
:表示默认操作,当没有其他 case 可执行时,执行该操作;
5、应用场景
多通道读取
func MultiChannelRead() {
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
ch1 <- 1
}()
go func() {
ch2 <- 2
}()
select {
case data := <-ch1:
fmt.Println("received data from ch1: ", data)
case data := <-ch2:
fmt.Println("receiver data from ch2: ", data)
}
}
多通道写入
func MultiChannelWrite() {
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
ch1 <- 1
}()
go func() {
ch2 <- 2
}()
select {
case <-ch1:
fmt.Println("Send data to ch1")
case <-ch2:
fmt.Println("Send data to ch2")
}
}
超时控制
func TimeoutControl() {
ch := make(chan int)
go func() {
time.Sleep(2 * time.Second)
}()
select {
case data := <-ch:
fmt.Println("Received data: ", data)
case <-time.After(1 * time.Second):
fmt.Println("Timeout occurred")
}
}