select 语句详解

select 语句详解

1. 基本语法和工作原理

go 复制代码
select {
case <-ch1:
    // ch1 有数据时执行
case msg := <-ch2:
    // ch2 有数据时执行,并赋值给 msg
case ch3 <- value:
    // 可以向 ch3 发送数据时执行
default:
    // 所有 case 都不满足时立即执行(非阻塞)
}

2. 核心特性

特性 说明
阻塞等待 没有 default 时,select 会阻塞直到某个 case 可执行
随机选择 多个 case 同时满足时,随机选择一个执行
非阻塞 有 default 时,如果没有 case 满足,立即执行 default
一次执行 只执行一个 case,然后退出 select

3. 在你的代码中的应用

回到你的原始代码:

go 复制代码
func fetchData(ctx context.Context) (string, error) {
    select {
    case <-ctx.Done():                    // case 1: 等待取消信号
        return "", ctx.Err()
    case <-time.After(2 * time.Second):   // case 2: 等待2秒定时器
        return "data", nil
    }
}

执行流程

  1. 1秒超时 (在 handler 中设置):ctx.Done() 先触发 → 返回错误
  2. 无超时time.After(2s) 先触发 → 返回数据

4. 常见使用模式

模式1:超时控制

go 复制代码
select {
case result := <-workChannel:
    return result
case <-time.After(5*time.Second):
    return nil, errors.New("timeout")
}

模式2:心跳检测

go 复制代码
ticker := time.NewTicker(1*time.Second)
for {
    select {
    case <-ticker.C:
        sendHeartbeat()
    case data := <-dataChannel:
        processData(data)
    }
}

模式3:优雅关闭

go 复制代码
select {
case <-ctx.Done():
    return ctx.Err()  // 被取消
case <-workDone:
    return nil        // 工作完成
}

模式4:非阻塞检查

go 复制代码
select {
case msg := <-ch:
    fmt.Println("Got:", msg)
default:
    fmt.Println("No message")
}

5. 重要注意事项

  1. nil channel:对 nil channel 的操作会永远阻塞
  2. 关闭的channel:从关闭的 channel 读取会立即返回零值
  3. 无缓冲vs有缓冲:影响发送操作的阻塞行为
  4. goroutine 泄露:未正确关闭 channel 可能导致 goroutine 泄露

6. 性能考虑

go 复制代码
// ❌ 避免:每次都创建新的 timer
select {
case <-time.After(1*time.Second):  // 每次调用都分配新对象
}

// ✅ 推荐:重用 timer
timer := time.NewTimer(1*time.Second)
defer timer.Stop()
select {
case <-timer.C:
}

7. 调试技巧

go 复制代码
select {
case <-ctx.Done():
    fmt.Println("Context cancelled:", ctx.Err())
case <-time.After(2*time.Second):
    fmt.Println("Timer expired")
default:
    fmt.Println("No case ready")
}

总结select 是 Go 并发编程的核心工具,特别适合处理超时、取消、心跳 等场景。你的代码用它实现了优雅的超时控制 - 这是非常经典和实用的模式!

相关推荐
小满zs8 小时前
Go语言第十一章(协程)
后端·go
newerp10 小时前
Golang 函数调用的瞬间:栈帧、拷贝栈与逃逸分析
后端·程序员·go
newerp10 小时前
Golang 网络轮询器 netpoll:把阻塞 IO 变成事件通知
后端·程序员·go
合橱瑰11 小时前
从“假智能”到“真闭环”:Go 向量推理引擎的零硬编码调优实践
后端·go
名字还没想好☜11 小时前
Go 的 TCP 粘包与拆包:用长度前缀协议 + bufio 正确读消息
后端·tcp/ip·golang·go·php
Zenova EdgeOS11 小时前
Go 工业边缘 Protobuf 实战:从 proto 到 Marshal 的完整落地
物联网·go·边缘计算·序列化·protobuf·工业边缘
MeetTanG1 天前
Go 实战锦囊|errgroup:优雅地管理并发任务组
后端·go
名字还没想好☜1 天前
Go 标准库 flag 包实战:参数解析、子命令、自定义 Value 类型与默认值
开发语言·后端·golang·go
天天喝旺仔2 天前
gRPC 流式通信实战:从一元调用到双向流,吃透四种 RPC 模式(Go 版)
分布式·http·微服务·rpc·go
明月_清风2 天前
从二叉树到 B+ 树:一文搞懂工程中「树」的演化之道
数据结构·算法·go