Skip to content

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 指针、操作类型(读/写/默认)、以及对应的值或接收变量地址。
  • 进入调度前,运行时会对所有非 defaultscase 做一次随机打乱,保证同一时刻多个就绪分支之间的公平性。

2. 检查就绪状态

  • 顺序遍历已打乱的 scase 列表,对每一条执行就绪检查:
    • 读分支:检查 channel 是否至少有一个元素可读
    • 写分支:检查 channel 是否至少可以写入(缓冲未满或有接收者等待)
  • 每发现一个就绪分支,就立即执行该分支逻辑并返回,不再检查后续分支。

3. 判断进入 default

  • 如果遍历完所有非 default 分支,都没有发现任何一个就绪:
    • 存在 default 分支:立刻执行 defaultselect 不会阻塞
    • 不存在 default:当前 goroutine 被挂起,等待任意一个 channel 就绪后再唤醒并重复上述流程

4. 非阻塞 vs 阻塞

  • defaultselect 变成非阻塞操作,对 CPU 来说类似一次快速轮询,一旦没准备好就走 default
  • defaultselect 会进入内核调度器的等待队列,直到有 channel 就绪,效率更高但可能需要更久才能继续

Released under the MIT License.