章节

10.4 select

本篇学习 select 对多个 channel 的等待方式,并能处理多个并发事件。

select

概念说明

select 用于同时等待多个 channel 操作。
哪个 case 的 channel 先准备好,就执行哪个分支。

如果多个分支同时可执行,Go 会随机选择一个。
因此不要把业务逻辑建立在固定选择顺序上。

语法/规则

  1. select 的每个 case 通常对应一次 channel 发送或接收。
  2. 如果没有任何 channel 准备好,select 会阻塞。
  3. 可以使用 default 分支避免阻塞。
  4. 可以配合 time.After 实现超时控制。
  5. 多个分支同时就绪时,执行哪个分支是不固定的。

select 同时等待多个消息示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
	"fmt"
	"time"
)

func main() {
	fast := make(chan string)
	slow := make(chan string)

	go func() {
		time.Sleep(50 * time.Millisecond)
		fast <- "fast channel"
	}()

	go func() {
		time.Sleep(100 * time.Millisecond)
		slow <- "slow channel"
	}()

	for i := 0; i < 2; i++ { // 一共接收两次消息
		select {
		case msg := <-fast:
			fmt.Println("receive:", msg) // fast 先准备好时,先进入这个分支
		case msg := <-slow:
			fmt.Println("receive:", msg) // slow 准备好时,进入这个分支
		}
	}
}

输出结果:

1
2
receive: fast channel
receive: slow channel

这个示例体现了什么

  1. select 会同时等待 fastslow 两个 channel。
  2. 哪个 channel 先准备好,就先执行哪个 case
  3. 第一次进入 select 时,fast 更早发送数据,所以先输出 fast channel
  4. 第二次进入 select 时,再接收 slow 里的数据。
  5. 如果多个 case 同时准备好,select 选择哪个分支是不固定的。

这段代码是怎么运行的

先看发送方:

1
2
3
4
5
6
7
8
9
go func() {
	time.Sleep(50 * time.Millisecond)
	fast <- "fast channel"
}()

go func() {
	time.Sleep(100 * time.Millisecond)
	slow <- "slow channel"
}()

可以先理解成:

1
2
50ms   fast 发送一条消息
100ms  slow 发送一条消息

再看接收方:

1
2
3
4
5
6
7
8
for i := 0; i < 2; i++ {
	select {
	case msg := <-fast:
		fmt.Println("receive:", msg)
	case msg := <-slow:
		fmt.Println("receive:", msg)
	}
}

它不是“for 一直空转检查”。
真正等待消息的是 select

运行过程可以理解成:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 1 次循环
-> 进入 select
-> fast  slow 都还没消息
-> select 阻塞等待
-> 50ms  fast 收到消息
-> 执行 case msg := <-fast

 2 次循环
-> 再次进入 select
-> slow 还没消息
-> select 继续阻塞等待
-> 100ms  slow 收到消息
-> 执行 case msg := <-slow

所以这里:

1
for i := 0; i < 2; i++

表示“总共接收 2 次消息”。

如果你不知道要接收多少次,而是想一直等,就会写成:

1
2
3
4
5
6
7
8
for {
	select {
	case msg := <-fast:
		fmt.Println("receive:", msg)
	case msg := <-slow:
		fmt.Println("receive:", msg)
	}
}

常见错误

  1. 以为 select 会按 case 书写顺序检查,实际多个分支同时就绪时会随机选择。
  2. 没有 default 且所有 channel 都不就绪,导致程序阻塞。
  3. default 分支里写空循环,可能造成 CPU 空转。
  4. select 和普通 switch 混淆,select 专门用于 channel 操作。
本文禁止转载
使用 Hugo 构建
主题 StackJimmy 设计 由 Hobin 魔改
最近构建时间:2026-04-17 19:07:48 CST
载入天数...载入时分秒...
发表了 1 篇文章 · 发表了 152 篇笔记 · 总计 18 万 0 千字