Day 13|select 与多路复用(并发控制的关键工具)[Go 语言 30 天系统学习计划]

学习目标

  • 理解 select 在 Go 并发模型中的定位
  • 掌握 select 的执行规则与调度行为
  • 学会用 select 处理多 channel 协作
  • 正确使用超时、default 分支避免死锁

一、为什么需要 select

在 Day 12 中你已经学会:

  • channel 可以在 goroutine 之间通信
  • 发送 / 接收可能发生阻塞

问题随之而来:

  • 如果我需要同时等待多个 channel怎么办?
  • 如果我希望有一个就先处理怎么办?
  • 如果我不想被永久阻塞怎么办?

答案:select

select 是 Go 提供的多路 channel 等待与调度机制


二、select 的基本语法

基本结构:

select {
case v := <-ch1:
    // 处理 ch1
case ch2 <- v:
    // 向 ch2 发送
default:
    // 可选分支
}

说明:

  • 每个 case 必须是 channel 发送或接收
  • select 会选择当前可执行的 case
  • 如果有多个可执行 case,会随机选择一个

三、select 的执行规则(必须理解)

select 的执行规则可以总结为:

  • 检查所有 case 中的 channel 操作
  • 选择一个不阻塞的 case 执行
  • 如果有多个不阻塞,随机选择
  • 如果都阻塞:
    • 有 default:执行 default
    • 无 default:当前 goroutine 阻塞

工程结论:select 本身不保证公平,只保证随机性。


四、同时等待多个 channel

示例:

func worker(ch1, ch2 chan int) {
    select {
    case v := <-ch1:
        fmt.Println("from ch1:", v)
    case v := <-ch2:
        fmt.Println("from ch2:", v)
    }
}

说明:

  • 哪个 channel 先就绪,就处理哪个
  • 另一个不会被阻塞等待

工程场景:多数据源、多事件监听。


五、select + for:并发处理的常见组合

select 往往和 for 搭配使用:

for {
    select {
    case v := <-ch:
        fmt.Println(v)
    }
}

这表示:

  • 持续监听 channel
  • 只要有数据就处理

注意:如果 channel 永远不会再有数据且未关闭,这个 goroutine 会永久阻塞。


六、select + default:非阻塞操作

加入 default 分支:

select {
case v := <-ch:
    fmt.Println(v)
default:
    fmt.Println("no data")
}

行为:

  • 如果 ch 当前可读,执行 case
  • 否则立即执行 default

工程警告:

  • default 会让 select 永远不阻塞
  • 放在 for 中极易造成 CPU 空转

错误示例:

for {
    select {
    default:
        // 空转,占满 CPU
    }
}

七、select 实现超时控制

最常见、最重要的用法之一:超时。

示例:

select {
case v := <-ch:
    fmt.Println("recv:", v)
case <-time.After(time.Second):
    fmt.Println("timeout")
}

说明:

  • time.After 返回一个 channel
  • 超时后该 channel 会被发送一个信号

工程场景:

  • 请求超时
  • 任务等待超时
  • 避免 goroutine 永久阻塞

八、select 与 channel 关闭的配合

当 channel 被关闭时:

  • 接收操作立即返回零值
  • select 中的接收 case 会被立即选中

示例:

select {
case v, ok := <-ch:
    if !ok {
        fmt.Println("channel closed")
        return
    }
    fmt.Println(v)
}

工程意义:select + close 是 goroutine 退出控制的重要组合。


九、select 的常见误区

  • 认为 select 有固定优先级(没有)
  • 在 for + select + default 中造成忙等
  • 忘记处理 channel 关闭导致死循环
  • select 里写复杂逻辑,难以维护

十、select 的工程定位总结

select 适合:

  • 多 channel 协作
  • 事件监听
  • 超时与取消
  • goroutine 生命周期控制

select 不适合:

  • 替代业务判断逻辑
  • 写复杂状态机(可读性会迅速下降)

一句话总结:

select 是 channel 的调度器,是并发控制的“中枢”。


预告:Day 14|sync 包与互斥原语(Mutex / RWMutex / Once)

下一天将系统讲解:

  • 为什么仍然需要锁
  • Mutex 与 RWMutex 的正确使用方式
  • Once 的真实用途
  • channel 与 mutex 的取舍原则

发表评论