不灭的焱

加密类型:SHA/AES/RSA下载Go
复合类型:切片(slice)、映射(map)、指针(pointer)、函数(function)、通道(channel)、接口(interface)、数组(array)、结构体(struct) Go类型+零值nil
引用类型:切片(slice)、映射(map)、指针(pointer)、函数(function)、通道(channel) Go引用

作者:AlbertWen  添加时间:2025-11-20 18:49:32  修改时间:2025-11-20 22:10:32  分类:02.Go语言编程  编辑

在 Go 语言中,select 语句用于处理多个通道的操作,它可以同时等待多个通道的读写事件,并在其中一个通道就绪时执行对应的分支。select 的用法类似于 switch,但专门用于通道操作,是并发编程中非常重要的工具。

一、基本语法

select {
case <-ch1:
    // 当 ch1 有数据可读时执行
case ch2 <- value:
    // 当 ch2 可写入数据时执行
case <-time.After(time.Second):
    // 超时处理(可选)
default:
    // 当所有通道都未就绪时执行(可选)
}
  • select 会同时监听所有 case 中的通道操作。
  • 当多个 case 同时就绪时,随机选择一个执行(避免饥饿问题)。
  • 如果所有 case 都未就绪:
    • 若有 default 分支,则立即执行 default
    • 若无 default 分支,则 select 会阻塞,直到某个 case 就绪。

二、常见用法

1. 等待多个通道,处理先就绪的

同时监听多个通道,哪个先有数据(或可写入)就处理哪个。

package main

import "fmt"

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

    go func() { ch1 <- 1 }()
    go func() { ch2 <- 2 }()

    select {
    case v := <-ch1:
        fmt.Println("ch1 就绪:", v)
    case v := <-ch2:
        fmt.Println("ch2 就绪:", v)
    }
}
// 输出可能是 "ch1 就绪: 1" 或 "ch2 就绪: 2"(随机)

2. 带超时的通道操作

使用 time.After 实现超时控制,避免永久阻塞。

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int)

    select {
    case v := <-ch:
        fmt.Println("收到数据:", v)
    case <-time.After(1 * time.Second): // 1秒后超时
        fmt.Println("超时")
    }
}
// 输出:超时(因为 ch 始终没有数据)

3. 非阻塞的通道操作

通过 default 实现非阻塞读写,避免阻塞当前 goroutine。

package main

import "fmt"

func main() {
    ch := make(chan int, 1)
    ch <- 10 // 通道已存数据

    // 非阻塞读
    select {
    case v := <-ch:
        fmt.Println("读取到:", v)
    default:
        fmt.Println("无法读取(通道为空)")
    }

    // 非阻塞写
    select {
    case ch <- 20:
        fmt.Println("写入成功")
    default:
        fmt.Println("无法写入(通道已满)")
    }
}
// 输出:
// 读取到: 10
// 无法写入(通道已满)

4. 关闭通道的处理

如果通道被关闭,读取操作会立即返回零值,可通过多返回值判断通道是否关闭。

package main

import "fmt"

func main() {
    ch := make(chan int)
    close(ch) // 关闭通道

    select {
    case v, ok := <-ch:
        if ok {
            fmt.Println("收到数据:", v)
        } else {
            fmt.Println("通道已关闭")
        }
    }
}
// 输出:通道已关闭

5. 无限等待多个通道

结合 for 循环,持续监听多个通道事件。

package main

import (
    "fmt"
    "time"
)

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

    go func() {
        for {
            time.Sleep(1 * time.Second)
            ch1 <- "来自 ch1 的消息"
        }
    }()

    go func() {
        for {
            time.Sleep(2 * time.Second)
            ch2 <- "来自 ch2 的消息"
        }
    }()

    // 持续监听两个通道
    for {
        select {
        case msg := <-ch1:
            fmt.Println(msg)
        case msg := <-ch2:
            fmt.Println(msg)
        }
    }
}
// 输出(间隔性):
// 来自 ch1 的消息
// 来自 ch1 的消息
// 来自 ch2 的消息
// ...

三、注意事项

  1. select 中的 case 必须是通道操作(读或写),不能是普通表达式。
  2. 若 select 中没有任何 case(空 select),会导致当前 goroutine 永久阻塞(deadlock)。
  3. 当通道被关闭后,读取操作会立即触发(返回零值),需通过 ok 判断是否有效数据。
  4. time.After 会创建一个新通道,超时后发送当前时间,需注意其生命周期(避免内存泄漏)。

select 是 Go 并发模型中协调多个通道的核心机制,灵活使用可实现高效的事件驱动逻辑。