Skip to content
On this page

Select 语句

Select 语句用于同时等待多个 Channel 操作,是 Go 并发编程的重要工具。

Select 基础

基本语法

go
package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    
    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- "来自 ch1"
    }()
    
    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "来自 ch2"
    }()
    
    select {
    case msg1 := <-ch1:
        fmt.Println(msg1)
    case msg2 := <-ch2:
        fmt.Println(msg2)
    }
}

随机选择

当多个 case 都就绪时,select 会随机选择一个。

go
func main() {
    ch1 := make(chan int, 1)
    ch2 := make(chan int, 1)
    
    ch1 <- 1
    ch2 <- 2
    
    select {
    case v := <-ch1:
        fmt.Println("ch1:", v)
    case v := <-ch2:
        fmt.Println("ch2:", v)
    }
}

Default 分支

非阻塞操作

go
func main() {
    ch := make(chan int)
    
    select {
    case value := <-ch:
        fmt.Println("收到:", value)
    default:
        fmt.Println("没有数据")
    }
}

发送不阻塞

go
func main() {
    ch := make(chan int, 1)
    
    select {
    case ch <- 1:
        fmt.Println("发送成功")
    default:
        fmt.Println("发送失败")
    }
}

超时处理

time.After

go
func main() {
    ch := make(chan string)
    
    go func() {
        time.Sleep(2 * time.Second)
        ch <- "结果"
    }()
    
    select {
    case result := <-ch:
        fmt.Println(result)
    case <-time.After(1 * time.Second):
        fmt.Println("超时")
    }
}

time.NewTimer

go
func main() {
    ch := make(chan string)
    timer := time.NewTimer(1 * time.Second)
    
    go func() {
        time.Sleep(2 * time.Second)
        ch <- "结果"
    }()
    
    select {
    case result := <-ch:
        fmt.Println(result)
    case <-timer.C:
        fmt.Println("超时")
    }
}

可重置超时

go
func main() {
    ch := make(chan string)
    timer := time.NewTimer(1 * time.Second)
    
    go func() {
        for i := 0; i < 3; i++ {
            time.Sleep(500 * time.Millisecond)
            ch <- fmt.Sprintf("消息 %d", i)
        }
    }()
    
    for {
        select {
        case msg := <-ch:
            fmt.Println(msg)
            timer.Reset(1 * time.Second)
        case <-timer.C:
            fmt.Println("超时")
            return
        }
    }
}

多 Channel 监听

监听多个 Channel

go
func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    done := make(chan bool)
    
    go func() {
        for i := 0; i < 3; i++ {
            ch1 <- fmt.Sprintf("ch1-%d", i)
            time.Sleep(100 * time.Millisecond)
        }
    }()
    
    go func() {
        for i := 0; i < 3; i++ {
            ch2 <- fmt.Sprintf("ch2-%d", i)
            time.Sleep(150 * time.Millisecond)
        }
    }()
    
    go func() {
        time.Sleep(1 * time.Second)
        done <- true
    }()
    
    for {
        select {
        case msg1 := <-ch1:
            fmt.Println(msg1)
        case msg2 := <-ch2:
            fmt.Println(msg2)
        case <-done:
            fmt.Println("完成")
            return
        }
    }
}

优先级处理

go
func main() {
    high := make(chan string)
    low := make(chan string)
    
    go func() {
        for i := 0; i < 5; i++ {
            low <- fmt.Sprintf("低优先级 %d", i)
            time.Sleep(100 * time.Millisecond)
        }
    }()
    
    go func() {
        time.Sleep(250 * time.Millisecond)
        high <- "高优先级"
    }()
    
    for {
        select {
        case msg := <-high:
            fmt.Println(msg)
            return
        case msg := <-low:
            fmt.Println(msg)
        }
    }
}

空 Select

go
func blockForever() {
    select {}
}

func main() {
    go blockForever()
    time.Sleep(time.Second)
    fmt.Println("仍在运行")
}

Select 模式

模式 1:超时控制

go
func withTimeout(ch <-chan int, timeout time.Duration) (int, error) {
    select {
    case value := <-ch:
        return value, nil
    case <-time.After(timeout):
        return 0, fmt.Errorf("超时")
    }
}

模式 2:心跳检测

go
func heartbeat(interval time.Duration) <-chan time.Time {
    ch := make(chan time.Time)
    go func() {
        ticker := time.NewTicker(interval)
        defer ticker.Stop()
        for now := range ticker.C {
            select {
            case ch <- now:
            default:
            }
        }
    }()
    return ch
}

func main() {
    heartbeatCh := heartbeat(500 * time.Millisecond)
    
    for i := 0; i < 5; i++ {
        select {
        case now := <-heartbeatCh:
            fmt.Println("心跳:", now.Format("15:04:05"))
        }
    }
}

模式 3:取消操作

go
func worker(ch <-chan int, done <-chan struct{}) {
    for {
        select {
        case value := <-ch:
            fmt.Println("处理:", value)
        case <-done:
            fmt.Println("收到取消信号")
            return
        }
    }
}

func main() {
    ch := make(chan int)
    done := make(chan struct{})
    
    go worker(ch, done)
    
    go func() {
        for i := 0; i < 5; i++ {
            ch <- i
            time.Sleep(100 * time.Millisecond)
        }
        close(done)
    }()
    
    time.Sleep(2 * time.Second)
}

Select 最佳实践

1. 总是包含 default 或超时

go
// 推荐
select {
case value := <-ch:
    return value
case <-time.After(timeout):
    return defaultValue
}

// 不推荐(可能永久阻塞)
select {
case value := <-ch:
    return value
}

2. 避免在循环中创建 Timer

go
// 推荐
timer := time.NewTimer(timeout)
defer timer.Stop()

for {
    select {
    case value := <-ch:
        return value
    case <-timer.C:
        return defaultValue
    }
}

// 不推荐
for {
    select {
    case value := <-ch:
        return value
    case <-time.After(timeout):  // 每次创建新 Timer
        return defaultValue
    }
}

3. 使用空 Select 阻塞

go
func block() {
    select {}  // 永久阻塞
}

实战示例

示例 1:多路复用

go
func multiplex(channels ...<-chan int) <-chan int {
    out := make(chan int)
    
    go func() {
        defer close(out)
        
        cases := make([]reflect.SelectCase, len(channels))
        for i, ch := range channels {
            cases[i] = reflect.SelectCase{
                Dir:  reflect.SelectRecv,
                Chan: reflect.ValueOf(ch),
            }
        }
        
        for len(cases) > 0 {
            chosen, value, ok := reflect.Select(cases)
            if !ok {
                cases = append(cases[:chosen], cases[chosen+1:]...)
                continue
            }
            out <- int(value.Int())
        }
    }()
    
    return out
}

示例 2:速率限制

go
func rateLimit(ch <-chan int, rate time.Duration) <-chan int {
    out := make(chan int)
    ticker := time.NewTicker(rate)
    defer ticker.Stop()
    
    go func() {
        defer close(out)
        for value := range ch {
            <-ticker.C
            out <- value
        }
    }()
    
    return out
}

func main() {
    ch := make(chan int)
    
    go func() {
        for i := 0; i < 10; i++ {
            ch <- i
        }
        close(ch)
    }()
    
    limited := rateLimit(ch, 500*time.Millisecond)
    
    for value := range limited {
        fmt.Println(value)
    }
}

练习

练习 1:竞速条件

实现两个 goroutine 竞速,先完成的返回结果。

答案
go
package main

import (
    "fmt"
    "time"
)

func race(id int, ch chan<- int) {
    time.Sleep(time.Duration(id) * 100 * time.Millisecond)
    ch <- id
}

func main() {
    ch := make(chan int)
    
    for i := 1; i <= 5; i++ {
        go race(i, ch)
    }
    
    winner := <-ch
    fmt.Printf("获胜者: %d\n", winner)
}

总结

  • Select 等待多个 Channel 操作
  • 随机选择就绪的 case
  • Default 实现非阻塞操作
  • time.After 实现超时
  • 支持多 Channel 监听
  • 避免在循环中创建 Timer

下一章:并发模式

基于 MIT 许可发布