Go 入门(22):select 多路复用——同时监听多个 channel
更新时间:2026-09-01。本文是
languages/go/beginner/入门层第 22 篇,接 channel 基础。如果你需要同时等待多个 channel,select 就是你的工具——哪个准备好就处理哪个。这是 Go 并发控制的基础。
本文要回答的问题
- select 语法是什么?什么时候用?
- 多个 case 都 ready,select 怎么选?
- 怎么用 select 实现超时?
- 空 select
select{}做什么?会死锁吗?
一、基本语法
go
select {
case <-ch1:
// ch1 有数据可以读,处理
case ch2 <- x:
// 可以发送 x 到 ch2,处理
default:
// 没有 case ready,执行 default
}规则:
- select 会阻塞,直到某个 case 能执行
- 多个 case 都 ready,随机选一个执行
- 有 default,没有 case ready 就执行 default,不阻塞
二、示例:同时从两个 channel 读
go
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
time.Sleep(1 * time.Second)
ch1 <- 1
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- 2
}()
for i := 0; i < 2; i++ {
select {
case v := <-ch1:
fmt.Println("got from ch1:", v)
case v := <-ch2:
fmt.Println("got from ch2:", v)
}
}
// 输出:
// got from ch1: 1 (等 1 秒)
// got from ch2: 2 (再等 1 秒)三、实用场景:超时
怎么实现"最多等 3 秒,超时就放弃"?
go
ch := make(chan Result, 1)
go func() {
result := doSlowThing()
ch <- result
}()
select {
case result := <-ch:
// 正常拿到结果
useResult(result)
case <-time.After(3 * time.Second):
// 超时了,返回错误
return fmt.Errorf("timeout after 3 seconds")
}time.After(d) 返回一个 channel,d 时间后会发一次值。select 先收到哪个就执行哪个——完美实现超时。
四、实用场景:防止阻塞,轮询
用 default 做非阻塞读写:
go
select {
case v := <-ch:
fmt.Println("got", v)
default:
fmt.Println("channel is empty, do other things")
}这就是非阻塞接收——如果 channel 里有数据就拿,没有就不阻塞,立刻执行 default。
五、空 select select{}
select{} 永远阻塞,因为没有 case 能执行。这会导致死锁:
go
func main() {
select {}
}
// fatal error: all goroutines are asleep - deadlock!什么时候用?比如你想让 main goroutine 永远不退出,让其他 goroutine 干活:
go
func main() {
// 启动一些服务,比如 HTTP server
go startServer()
select {} // 永远阻塞,不退出
}现在 Go 一般用 signal.Notify 监听退出信号,比空 select 好。
六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 忘记关闭 channel | select 一直阻塞 | 确保 channel 最终会关闭 |
| 多个 case 都 ready 期望按顺序走 | 其实随机选一个 | 不要依赖顺序,每个 case 独立 |
| 超时不用 time.After | 写复杂的定时器自己控制 | time.After 最简单直接 |
| 空 select 意外死锁 | 程序不干活直接死锁 | 不要随便写 select{} |
相关与延伸
下一篇:sync 包——Mutex、Once、Pool;select 底层实现在进阶层讲;channel 和 select 配合实现 fan-out fan-in 等并发模式,进阶层讲。
一句话总结
select 多路复用:同时监听多个 channel,哪个 case ready 就执行哪个;多个 case 都 ready 随机选一个;time.After 配合 select 实现超时非常方便;default 做非阻塞读写;空 select 永远阻塞;这是 Go 并发控制最常用的工具。