本节摘要:select 是并发世界的多路复用器——同时监听多个 channel 操作,哪个就绪执行哪个,全部就绪时随机挑一个保证公平。本节覆盖基本语法与随机选择机制、default 的非阻塞语义、超时控制两套写法、Context 取消的优雅退出、定时任务与 nil 通道禁用分支的技巧,最后谈性能与"分支内不做重活"的纪律。
阅读完本节,你应当能够:
select { case v := <-chA: fmt.Println("来自A", v) case chB <- 42: fmt.Println("发给了B") }
外形像 switch,语义完全不同:switch 按值匹配,select 按"就绪"匹配。规则三条:
随机选择不是装饰——若固定选第一个,饥饿的永远是排在后面的通道,流量会系统性偏斜。随机是公平性的实现,而非"真随机"的需求。
select { case v := <-queue: handle(v) default: // 队列空,不等待,先干别的 }
发送侧同理,"满了就丢、丢了记数"的降级队列就是 default 一行的事。
整体超时:等多久都行,但最多等 N 秒。标准库 time 包的 After 函数在到点时向返回的通道发一个值,与业务通道同台竞技:
select { case res := <-doWork(): fmt.Println("结果", res) case <-time.After(2 * time.Second): fmt.Println("超时放弃") return errors.New("超时") }
每次操作超时:用 NewTicker 不合适,正确工具是定时器的循环再装填模式——每处理一条消息重置计时。周期任务则用 Ticker:
ticker := time.NewTicker(500 * time.Millisecond) defer ticker.Stop() for range ticker.C { // 每0.5秒醒来一次 report() }
⚠️ 常见坑:循环里反复调用 time.After 会为每次迭代创建新定时器,循环直到超时才释放,高频循环下定时器堆积。长期运行的超时逻辑用 NewTimer 加 Reset 重用,或直接用下文的 Context。

生产代码的超时与取消首选 Context(下一节细讲),select 是它的接收端:
func worker(ctx context.Context, in <-chan Task) { for { select { case t := <-in: process(t) case <-ctx.Done(): // 取消或超时到达 fmt.Println("退出原因:", ctx.Err()) return } } }
这个两分支 select 是 Go 服务里出现频率最高的循环骨架之一:正常流走 in,控制流走 Done。
回顾上节:nil 通道收发永久阻塞。放在 select 里,"永久阻塞的分支"等于"不存在"。于是有优雅收工术——多路消费循环里某通道耗尽后置 nil,select 自动只服务剩下的通道,全部耗尽后走退出分支。比起为每个通道维护单独的开关变量,这个技巧零额外状态:
for a != nil || b != nil { select { case v, ok := <-a: if !ok { a = nil; continue } handle(v) case v, ok := <-b: if !ok { b = nil; continue } handle(v) } }
select 本身的开销很低——它就是运行时的一次多路等待。真正伤人的是分支里做重活:分支是"接到通知后的动作",不是"处理车间"。在分支里做耗时计算或再发起阻塞调用,等于占着分派台干活,后面的消息全部排队。规矩:分支里只做"取数据、转手、快速标记",重处理丢给别的 goroutine 或工作池。
| 需求 | 写法 |
|---|---|
| 多通道取数据 | select 加各接收分支 |
| 非阻塞尝试 | 加 default |
| 一次性超时 | select 加定时器分支 |
| 周期任务 | Ticker 加循环 |
| 可取消循环 | select 加 Done 分支 |
| 动态停用分支 | 通道变量置 nil |
💡 关键直觉:select 是前台接待,channel 是分机线——接待员只做转接(分派),不替客人办事(耗时逻辑),否则大堂堵死。
问答三则。select 能监听普通变量吗? 不能,只认通道操作——这正是"通信共享内存"的句法体现。空 select 会怎样? 永久阻塞(没有分支可就绪),偶尔被用作"卡住不退出"的调试手段,生产代码别这么写。两个分支就绪的概率真是五五开吗? 运行时做的是伪随机均匀选择,统计意义近似均等,但不构成任何顺序保证——需要优先级就拆成两个 select 嵌套,先试高优先级带 default,再统一等。
下一节回到共享内存这条老路:sync 同步原语。