select 语句
select用于监听多个channel
基础语法:
go
select {
case 表达式1:
做点什么1
case 表达式2:
做点什么2
...
default:
做点什么n
}其中,每个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 就绪,效率更高但可能需要更久才能继续