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

相关推荐
爱好曙光1 小时前
Go GMP调度器调优:CPU绑定与goroutine饥饿实战
性能优化·go·并发编程
景威6 小时前
写了个零依赖的 Go 版本管理器,curl | bash 完事,支持自动回退国内镜像
go
cocoCola916676 小时前
Go语言操作Redis
go
名字还没想好☜1 天前
Go 并发实战:用 channel 实现 worker pool
java·数据库·后端·golang·go
星航夜空的帆舟2 天前
ADK 会话与状态管理:三级状态、内存服务与多存储后端
go·agent
CodeStats2 天前
【编程语言】深度梳理C/C++、Java、Python、Go、Rust的区别
java·linux·c语言·c++·python·rust·go
用户6757049885022 天前
Go 程序员进阶必读:这 24 个经典的 Golang 错误,连老手都会栽跟头!
后端·go
大P哥阿豪2 天前
Go panic & recover:深入Go语言的异常拯救之道
开发语言·后端·golang·go
星航夜空的帆舟2 天前
ADK 模型抽象与多模型集成:从 Gemini 到 Ollama
go·agent
XCAI8888883 天前
性价比高的郑州geo哪个靠谱
go