学习目标
- 理解 select 在 Go 并发模型中的定位
- 掌握 select 的执行规则与调度行为
- 学会用 select 处理多 channel 协作
- 正确使用超时、default 分支避免死锁
一、为什么需要 select
在 Day 12 中你已经学会:
- channel 可以在 goroutine 之间通信
- 发送 / 接收可能发生阻塞
问题随之而来:
- 如果我需要同时等待多个 channel怎么办?
- 如果我希望有一个就先处理怎么办?
- 如果我不想被永久阻塞怎么办?
答案:select
select 是 Go 提供的多路 channel 等待与调度机制。
二、select 的基本语法
基本结构:
select {
case v := <-ch1:
// 处理 ch1
case ch2 <- v:
// 向 ch2 发送
default:
// 可选分支
}说明:
- 每个 case 必须是 channel 发送或接收
- select 会选择当前可执行的 case
- 如果有多个可执行 case,会随机选择一个
三、select 的执行规则(必须理解)
select 的执行规则可以总结为:
- 检查所有 case 中的 channel 操作
- 选择一个不阻塞的 case 执行
- 如果有多个不阻塞,随机选择
- 如果都阻塞:
- 有 default:执行 default
- 无 default:当前 goroutine 阻塞
工程结论:select 本身不保证公平,只保证随机性。
四、同时等待多个 channel
示例:
func worker(ch1, ch2 chan int) {
select {
case v := <-ch1:
fmt.Println("from ch1:", v)
case v := <-ch2:
fmt.Println("from ch2:", v)
}
}说明:
- 哪个 channel 先就绪,就处理哪个
- 另一个不会被阻塞等待
工程场景:多数据源、多事件监听。
五、select + for:并发处理的常见组合
select 往往和 for 搭配使用:
for {
select {
case v := <-ch:
fmt.Println(v)
}
}这表示:
- 持续监听 channel
- 只要有数据就处理
注意:如果 channel 永远不会再有数据且未关闭,这个 goroutine 会永久阻塞。
六、select + default:非阻塞操作
加入 default 分支:
select {
case v := <-ch:
fmt.Println(v)
default:
fmt.Println("no data")
}行为:
- 如果 ch 当前可读,执行 case
- 否则立即执行 default
工程警告:
- default 会让 select 永远不阻塞
- 放在 for 中极易造成 CPU 空转
错误示例:
for {
select {
default:
// 空转,占满 CPU
}
}七、select 实现超时控制
最常见、最重要的用法之一:超时。
示例:
select {
case v := <-ch:
fmt.Println("recv:", v)
case <-time.After(time.Second):
fmt.Println("timeout")
}说明:
time.After返回一个 channel- 超时后该 channel 会被发送一个信号
工程场景:
- 请求超时
- 任务等待超时
- 避免 goroutine 永久阻塞
八、select 与 channel 关闭的配合
当 channel 被关闭时:
- 接收操作立即返回零值
- select 中的接收 case 会被立即选中
示例:
select {
case v, ok := <-ch:
if !ok {
fmt.Println("channel closed")
return
}
fmt.Println(v)
}工程意义:select + close 是 goroutine 退出控制的重要组合。
九、select 的常见误区
- 认为 select 有固定优先级(没有)
- 在 for + select + default 中造成忙等
- 忘记处理 channel 关闭导致死循环
- select 里写复杂逻辑,难以维护
十、select 的工程定位总结
select 适合:
- 多 channel 协作
- 事件监听
- 超时与取消
- goroutine 生命周期控制
select 不适合:
- 替代业务判断逻辑
- 写复杂状态机(可读性会迅速下降)
一句话总结:
select 是 channel 的调度器,是并发控制的“中枢”。
预告:Day 14|sync 包与互斥原语(Mutex / RWMutex / Once)
下一天将系统讲解:
- 为什么仍然需要锁
- Mutex 与 RWMutex 的正确使用方式
- Once 的真实用途
- channel 与 mutex 的取舍原则