3.10 Go select 语句学习笔记
1. select 基本用法 — 从多个 channel 接收
select 同时监听多个 channel 的操作,哪个 channel 先准备好就执行哪个 case:
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
// goroutine 1: 1秒后发送
go func() {
time.Sleep(time.Second)
ch1 <- "Hello from ch1"
}()
// goroutine 2: 2秒后发送
go func() {
time.Sleep(2 * time.Second)
ch2 <- "Hello from ch2"
}()
// select 等待,ch1 先准备好,执行 ch1 的 case
select {
case msg := <-ch1:
fmt.Println("Received from ch1:", msg)
case msg := <-ch2:
fmt.Println("Received from ch2:", msg)
}
}
执行结果:
Received from ch1: Hello from ch1
要点:
-
select与 switch 类似,但专门用于 channel 操作(发送/接收) - 每个 case 必须是 channel 操作:
<-ch1(接收)或ch1 <- value(发送) - ch1 的 goroutine 等待 1 秒,ch2 等待 2 秒,ch1 先准备好,所以 select 执行 ch1 的 case
- ch2 的数据仍在 channel 中未被接收(goroutine 会一直阻塞等待接收)
- select 只执行一个 case,不是所有 case 都执行(与 switch 的自动 break 一致)
2. select + default — 非阻塞接收
default case 在没有 channel 操作准备好时立即执行,实现非阻塞(不等待):
package main
import "fmt"
func main() {
ch3 := make(chan int) // 无缓冲 channel,没有数据
select {
case <-ch3:
fmt.Println("Received from ch3")
default:
fmt.Println("No data available")
}
}
执行结果:
No data available
要点:
-
ch3是无缓冲 channel,没有 goroutine 向它发送数据,所以<-ch3会阻塞 -
defaultcase 让 select 不阻塞等待,直接执行 default - 没有 default 的 select 会一直阻塞,直到某个 case 准备好
- 有 default 的 select 立即判断:有数据就接收,没数据就走 default,绝不阻塞
- 常见用法:轮询 channel(在循环中用 select + default 检查是否有数据)
- 也用于非阻塞发送:
select { case ch <- value: ... default: // channel 满,丢弃或缓存 }
3. select + time.After — 超时控制
time.After(duration) 返回一个 channel,在指定时间后发送当前时间,用于 select 超时:
package main
import (
"fmt"
"time"
)
func main() {
slow := make(chan string)
go func() {
time.Sleep(3 * time.Second) // 模拟慢操作
slow <- "Hello from slow"
}()
select {
case msg := <-slow:
fmt.Println("Received from slow:", msg)
case <-time.After(1 * time.Second):
fmt.Println("Timeout: slow channel took too long")
}
}
执行结果:
Timeout: slow channel took too long
要点:
-
time.After(1 * time.Second)返回<-chan time.Time,1秒后该 channel 收到一个时间值 - slow 的 goroutine 等待 3 秒才发送,但 select 的超时只有 1 秒
- 1 秒后
time.After的 channel 先准备好,select 执行超时 case - 这是 Go 中最常用的超时模式:给任何可能阻塞的操作加超时限制
- 实际场景:HTTP 请求超时、数据库查询超时、RPC 调用超时等
- 注意:超时后 slow 的 goroutine 仍在运行(只是 main 不再等待它),可能需要 context 取消
4. select 多 channel 同时就绪 — 随机选择
当多个 channel 同时准备好时,Go 随机选择一个 case 执行:
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
fast1 := make(chan int, 1) // 缓冲 channel,可以立即发送
fast2 := make(chan int, 1)
fast1 <- 1
fast2 <- 2
select {
case msg := <-fast1:
fmt.Println("Received from fast1:", msg)
case msg := <-fast2:
fmt.Println("Received from fast2:", msg)
}
}
}
执行结果(每次运行可能不同):
Received from fast2: 2
Received from fast2: 2
Received from fast1: 1
Received from fast1: 1
Received from fast1: 1
要点:
-
fast1 <- 1和fast2 <- 2使用缓冲 channel(容量1),发送不阻塞,两个 channel 同时有数据 - select 在两个 channel 都准备好时,随机选择一个执行,不是按顺序
- 5次循环中,fast1 和 fast2 各被选中若干次,分布不均匀(这是随机选择的结果)
- 每次运行结果可能不同(随机选择是 Go 的设计决策,避免对特定 channel 产生偏好)
- 为什么随机?防止某个 channel 因为总是排在前面而被优先处理,导致其他 channel 饿死(starvation)
- 每次循环创建新 channel:确保上次循环的接收不影响当前循环
5. select 用于发送 — 非阻塞选择发送目标
select 不仅用于接收,也可以用于发送,选择哪个 channel 发送数据:
package main
import "fmt"
func main() {
send1 := make(chan string, 1) // 缓冲容量1
send2 := make(chan string, 1)
// select 选择向哪个 channel 发送
select {
case send1 <- "Message1":
fmt.Println("Message1 sent to send1")
case send2 <- "Message2":
fmt.Println("Message2 sent to send2")
}
// 检查 send1 是否有数据
select {
case msg := <-send1:
fmt.Println("Retrieved from send1:", msg)
default:
fmt.Println("send1 is empty")
}
// 检查 send2 是否有数据
select {
case msg := <-send2:
fmt.Println("Retrieved from send2:", msg)
default:
fmt.Println("send2 is empty")
}
}
执行结果(send1 被选中):
Message1 sent to send1
Retrieved from send1: Message1
send2 is empty
要点:
-
case send1 <- "Message1":— 这是一个发送操作(向 send1 发送数据) - 两个缓冲 channel 容量都是1,都可以立即接收数据,所以 select 随机选择一个
- 本例选中了
send1 <- "Message1",所以 send1 有数据,send2 是空的 - 后面用
select + default非阻塞检查每个 channel:send1 有数据读取成功,send2 空走 default - select 同时支持发送和接收两种操作,可以混合使用:
select { case ch <- value: // 发送 case msg := <-ch2: // 接收 }
知识点总结
| 知识点 | 关键概念 |
|---|---|
| select 基本用法 | 同时监听多个 channel,先准备好先执行,只执行一个 case |
| select + default | 无数据时立即执行 default,实现非阻塞接收/发送 |
| select + time.After | 超时控制,time.After(d) 返回定时 channel,超时后走该 case |
| 多 channel 同时就绪 | Go 随机选择一个 case,避免某个 channel 被偏好/饿死 |
| select 发送操作 |
case ch <- value: 发送也可以用 select,随机选择发送目标 |