Go语言:select语句


[toc]

Go语言:select语句

语法

功能:select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。

语法:

select {
case clauseA:
    statementA
case clauseB:
    statementB
default:
    statementC
}

说明:

  • case 中表达式必须是 Channel 收发操作。
  • 无论哪一个表达式返回,都会立刻执行 case 中的代码。
  • 当 select 中两个 case 同时被触发时,会随机执行其中一个,从而避免饥饿问题发生。
  • default 语句:提供非阻塞的收发的特性。若存在可收发的 Channel ,直接处理该 case 的语句;否则,执行 default 中的语句。

select 能够让 Goroutine 同时等待多个 Channel 可读或可写,在多个文件或者 Channel 状态改变之前,select 会一直阻塞当前线程或 Goroutine 。

select 的几种使用场景

  1. 阻塞操作

    使用空的 select 语句,会直接阻塞当前 Goroutine,导致 Goroutine 进入无法被唤醒的永久休眠状态。

    func example() {
        select {}
    }
  2. 单一管道

    select 中只包含一个 case ,编译器会将 select 改写成 if 条件语句。若 case 中 Channel 是空指针时,会直接挂起当前 Goroutine 并陷入永久休眠。

    func example() {
        c := make(chan string)
        
        go func() {
            time.Sleep(time.Second)
            c <- "one message"
        }
        
        select {
        case msg := <- c:
            fmt.Println("received", msg)
        }
        // 等价于
        // if msg := <- c {
        //     fmt.Println("received", msg)
        // }
    }
  3. 非阻塞操作:

    select中包含多个case,且其中一个是 default 语句。若不存在可收发的 Channel ,直接执行 default 中的语句。

    func example() {
        c := make(chan string)
        
        select {
        case msg := <- c:
            fmt.Println("received", msg)
        default:
            fmt.Println("no message received")
        }
    }

编译器对 select 的处理

编译器对 select 语句的一般处理流程:

  1. 将所有 case 转换成包含 Channel 以及类型等信息的 runtime.scase 结构体。
  2. 调用 runtime.selectgo 从多个就绪的 Channel 中选择一个可执行的 runtime.scase 结构体。
  3. 通过 for 循环生成一组 if 语句,在语句中判断自己是不是被选中的 case 。

随机执行的关键运行时函数:runtime.selectgo ,它的执行步骤为,

  1. 确定 case 的处理顺序
    • 轮询顺序:通过 runtime.fastrandom 函数引入随机性。
    • 加锁顺序:按照 Channel 的地址排序后确定加锁顺序。
  2. 根据轮询顺序遍历所有的 case ,查看是否有可立即处理的 Channel 。
    • 如果存在,直接获取 case 对应的索引并返回
    • 否则,创建 runtime.sudog 结构体,并将当前 Goroutine 加入到所有相关的 Channel 收发队列,并调用 runtime.gopark 挂起当前 Goroutine ,等待调度器的唤醒。
  3. 当调度器唤醒当前 Goroutine 时,会再次按照加锁顺序遍历所有的 case ,从中查找需要被处理的 runtime.sudog 对应的索引。

select 关键字是 Go 语言特有的控制结构,需要编译器和运行时函数共同实现。


文章作者: algorithmofdish
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 algorithmofdish !
  目录