3.10 Go select 语句学习笔记

3.10 Go select 语句学习笔记

1. select 基本用法 — 从多个 channel 接收

select 同时监听多个 channel 的操作,哪个 channel 先准备好就执行哪个 case:

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    // goroutine 1: 1秒后发送
    go func() {
        time.Sleep(time.Second)
        ch1 <- "Hello from ch1"
    }()

    // goroutine 2: 2秒后发送
    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "Hello from ch2"
    }()

    // select 等待,ch1 先准备好,执行 ch1 的 case
    select {
    case msg := <-ch1:
        fmt.Println("Received from ch1:", msg)
    case msg := <-ch2:
        fmt.Println("Received from ch2:", msg)
    }
}

执行结果:

Received from ch1: Hello from ch1

要点

  • select 与 switch 类似,但专门用于 channel 操作(发送/接收)
  • 每个 case 必须是 channel 操作:<-ch1(接收)或 ch1 <- value(发送)
  • ch1 的 goroutine 等待 1 秒,ch2 等待 2 秒,ch1 先准备好,所以 select 执行 ch1 的 case
  • ch2 的数据仍在 channel 中未被接收(goroutine 会一直阻塞等待接收)
  • select 只执行一个 case,不是所有 case 都执行(与 switch 的自动 break 一致)

2. select + default — 非阻塞接收

default case 在没有 channel 操作准备好时立即执行,实现非阻塞(不等待):

package main

import "fmt"

func main() {
    ch3 := make(chan int) // 无缓冲 channel,没有数据
    select {
    case <-ch3:
        fmt.Println("Received from ch3")
    default:
        fmt.Println("No data available")
    }
}

执行结果:

No data available

要点

  • ch3 是无缓冲 channel,没有 goroutine 向它发送数据,所以 <-ch3 会阻塞
  • default case 让 select 不阻塞等待,直接执行 default
  • 没有 default 的 select 会一直阻塞,直到某个 case 准备好
  • 有 default 的 select 立即判断:有数据就接收,没数据就走 default,绝不阻塞
  • 常见用法:轮询 channel(在循环中用 select + default 检查是否有数据)
  • 也用于非阻塞发送:select { case ch <- value: ... default: // channel 满,丢弃或缓存 }

3. select + time.After — 超时控制

time.After(duration) 返回一个 channel,在指定时间后发送当前时间,用于 select 超时:

package main

import (
    "fmt"
    "time"
)

func main() {
    slow := make(chan string)
    go func() {
        time.Sleep(3 * time.Second) // 模拟慢操作
        slow <- "Hello from slow"
    }()

    select {
    case msg := <-slow:
        fmt.Println("Received from slow:", msg)
    case <-time.After(1 * time.Second):
        fmt.Println("Timeout: slow channel took too long")
    }
}

执行结果:

Timeout: slow channel took too long

要点

  • time.After(1 * time.Second) 返回 <-chan time.Time,1秒后该 channel 收到一个时间值
  • slow 的 goroutine 等待 3 秒才发送,但 select 的超时只有 1 秒
  • 1 秒后 time.After 的 channel 先准备好,select 执行超时 case
  • 这是 Go 中最常用的超时模式:给任何可能阻塞的操作加超时限制
  • 实际场景:HTTP 请求超时、数据库查询超时、RPC 调用超时等
  • 注意:超时后 slow 的 goroutine 仍在运行(只是 main 不再等待它),可能需要 context 取消

4. select 多 channel 同时就绪 — 随机选择

当多个 channel 同时准备好时,Go 随机选择一个 case 执行:

package main

import "fmt"

func main() {
    for i := 0; i < 5; i++ {
        fast1 := make(chan int, 1) // 缓冲 channel,可以立即发送
        fast2 := make(chan int, 1)
        fast1 <- 1
        fast2 <- 2
        select {
        case msg := <-fast1:
            fmt.Println("Received from fast1:", msg)
        case msg := <-fast2:
            fmt.Println("Received from fast2:", msg)
        }
    }
}

执行结果(每次运行可能不同):

Received from fast2: 2
Received from fast2: 2
Received from fast1: 1
Received from fast1: 1
Received from fast1: 1

要点

  • fast1 <- 1fast2 <- 2 使用缓冲 channel(容量1),发送不阻塞,两个 channel 同时有数据
  • select 在两个 channel 都准备好时,随机选择一个执行,不是按顺序
  • 5次循环中,fast1 和 fast2 各被选中若干次,分布不均匀(这是随机选择的结果)
  • 每次运行结果可能不同(随机选择是 Go 的设计决策,避免对特定 channel 产生偏好)
  • 为什么随机?防止某个 channel 因为总是排在前面而被优先处理,导致其他 channel 饿死(starvation)
  • 每次循环创建新 channel:确保上次循环的接收不影响当前循环

5. select 用于发送 — 非阻塞选择发送目标

select 不仅用于接收,也可以用于发送,选择哪个 channel 发送数据:

package main

import "fmt"

func main() {
    send1 := make(chan string, 1) // 缓冲容量1
    send2 := make(chan string, 1)

    // select 选择向哪个 channel 发送
    select {
    case send1 <- "Message1":
        fmt.Println("Message1 sent to send1")
    case send2 <- "Message2":
        fmt.Println("Message2 sent to send2")
    }

    // 检查 send1 是否有数据
    select {
    case msg := <-send1:
        fmt.Println("Retrieved from send1:", msg)
    default:
        fmt.Println("send1 is empty")
    }

    // 检查 send2 是否有数据
    select {
    case msg := <-send2:
        fmt.Println("Retrieved from send2:", msg)
    default:
        fmt.Println("send2 is empty")
    }
}

执行结果(send1 被选中):

Message1 sent to send1
Retrieved from send1: Message1
send2 is empty

要点

  • case send1 <- "Message1": — 这是一个发送操作(向 send1 发送数据)
  • 两个缓冲 channel 容量都是1,都可以立即接收数据,所以 select 随机选择一个
  • 本例选中了 send1 <- "Message1",所以 send1 有数据,send2 是空的
  • 后面用 select + default 非阻塞检查每个 channel:send1 有数据读取成功,send2 空走 default
  • select 同时支持发送和接收两种操作,可以混合使用:
    select {
    case ch <- value:    // 发送
    case msg := <-ch2:   // 接收
    }
    

知识点总结

知识点 关键概念
select 基本用法 同时监听多个 channel,先准备好先执行,只执行一个 case
select + default 无数据时立即执行 default,实现非阻塞接收/发送
select + time.After 超时控制,time.After(d) 返回定时 channel,超时后走该 case
多 channel 同时就绪 Go 随机选择一个 case,避免某个 channel 被偏好/饿死
select 发送操作 case ch <- value: 发送也可以用 select,随机选择发送目标
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容