select 语句
select
用于监听多个channel
基础语法:
go
select {
case 表达式1:
做点什么1
case 表达式2:
做点什么2
...
default:
做点什么3
}
其中,每个case
后书写的表达式必须是通道相关的操作,即发送和接收两种操作。
select
语句会监听所有的case
中使用的通道的操作
- 一旦有一个执行成功,就执行那个
case
下的代码块 - 如果所有通道都没有成功,就执行
default
- 如果多个通道都准备好,则随机选取一个
- 如果没有
defalut
,则会阻塞直到某个case
成功
深入底层
简单来说,就是打乱每个case
,然后进行一次轮询,有可以执行的就绪分支就干!没有就进default
。但是如果连default
都没有的话,就阻塞。继续轮询
编译器遇到一个 select
语句时,会为每个 case
(包括 default
)在运行时构建一个内部结构体 scase
,然后交给运行时的 runtime.selectgo
函数来决定执行哪一条分支
1. 构建和打乱 scase
列表
- 编译阶段,
select
中的每个case
都被转换成一个scase
结构,包含channel
指针、操作类型(读/写/默认)、以及对应的值或接收变量地址。 - 进入调度前,运行时会对所有非
default
的scase
做一次随机打乱,保证同一时刻多个就绪分支之间的公平性。
2. 检查就绪状态
- 顺序遍历已打乱的
scase
列表,对每一条执行就绪检查:- 读分支:检查 channel 是否至少有一个元素可读
- 写分支:检查 channel 是否至少可以写入(缓冲未满或有接收者等待)
- 每发现一个就绪分支,就立即执行该分支逻辑并返回,不再检查后续分支。
3. 判断进入 default
- 如果遍历完所有非
default
分支,都没有发现任何一个就绪:- 存在
default
分支:立刻执行default
,select
不会阻塞 - 不存在
default
:当前 goroutine 被挂起,等待任意一个 channel 就绪后再唤醒并重复上述流程
- 存在
4. 非阻塞 vs 阻塞
- 有
default
:select
变成非阻塞操作,对 CPU 来说类似一次快速轮询,一旦没准备好就走default
- 无
default
:select
会进入内核调度器的等待队列,直到有 channel 就绪,效率更高但可能需要更久才能继续