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

相关推荐
我不会起名字32222 分钟前
Redis 从 0 到 1(二十八):Redis 抖一下,重试把它打挂了——超时/重试/熔断的 Go 封装
redis·go·超时·熔断·重试
xyzzklk2 小时前
变相解决华为Mate 8锁屏后无法建立入站网络连接
linux·运维·服务器·网络·华为·go·安卓
vipxieliang1 天前
语言对比卡:PHP vs Go vs Java 的优缺点、适用场景与「为什么选 PHP」
java·go·php
我不会起名字3222 天前
Redis 从 0 到 1(二十七):JSON 体积是 Protobuf 的 3.7 倍?3 种序列化的 Go 基准测试
redis·go·序列化·protobuf·messagepack
我不会起名字3222 天前
Redis 从 0 到 1(二十三):一个热 key 打到 10 万 QPS——hotkey 与本地缓存的 Go 实现
redis·缓存·go·qps·hotkey
Patrick_Wilson3 天前
Go 为什么需要 defer?从函数怎么收尾说起
后端·go·编程语言
Patrick_Wilson3 天前
从文本输出理解 Go 方法与接口
后端·go·编程语言
Patrick_Wilson3 天前
读懂一个 Go JSON 处理函数:从输入数据追踪结构体、校验与错误返回
go·json·编程语言
Patrick_Wilson3 天前
第一次读 Go 项目:从两个源码文件弄清 package、import 和 go.mod
后端·go·编程语言
Patrick_Wilson3 天前
执行 go build . 后,Go 怎样找到代码并生成程序
后端·go·编程语言