Go channel | 使用反射动态管理多个 select case

127 阅读1分钟

select 语句可以处理 channel 的 send 和 recv,send 和 recv 都可以作为 case 分支。如果我们需要处理的 case 分支 数量少,那么使用 select 语句是完全可以的,例如:

select {
case msg1 := <-ch1:
    fmt.Println(msg1)
case msg2 := <-ch2:
    fmt.Println(msg2)
}

如果需要处理很多个 case 分支,例如 100 个、1000 个,那么就可以使用反射来管理多个 case 分支

func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool)

你可以将一组 SelectCase 传入,SelectCase 的定义如下:

const (
    _             SelectDir = iota
    SelectSend              // case Chan <- Send
    SelectRecv              // case <-Chan:
    SelectDefault           // default
)

type SelectCase struct {
    // 表示 case 的方向,即操作的类型
    // SelectSend:表示这是一个发送操作(向 channel 发送数据)
    // SelectRecv:表示这是一个接收操作(从 channel 接收数据)
    // SelectDefault:表示这是一个默认操作(类似于 select 中的 default 分支)。
    Dir  SelectDir 
    Chan Value     // 要操作的 channel
    Send Value     // 要发送的值
}

再来解释一下返回值:

  • chosen 表示被选中的 case 的索引(即 cases 切片中的下标);
  • recv 是从 channel 接收到的值(如果选中的 case 是一个接收操作),否则 recv 是零值;
  • recvOK 表示是否从 channel 接收到值,而不是由于 channel 关闭接收到的零值(也就是说,当 recvOK 为 true 时则代表 channel 未被关闭)。

最后我们以一个 reflect.Select 的使用示例收尾:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    numChannels := 10
    channels := make([]chan int, numChannels)
    for i := 0; i < numChannels; i++ {
        channels[i] = make(chan int, 1)
    }

    // 动态构建 SelectCase 切片
    var cases []reflect.SelectCase
    for i, ch := range channels {
        cases = append(cases, reflect.SelectCase{
            Dir:  reflect.SelectRecv, // 接收操作
            Chan: reflect.ValueOf(ch),
        }, reflect.SelectCase{
            Dir:  reflect.SelectSend, // 发送操作
            Chan: reflect.ValueOf(ch),
            Send: reflect.ValueOf(i),
        })
    }

    for i := 0; i < numChannels; i++ {
        // 使用 reflect.Select 处理多个 channel
        chosen, recv, recvOK := reflect.Select(cases)

        // 输出结果
        if recv.IsValid() {
            fmt.Printf("Received value: %v, recvOK: %v\n", recv.Int(), recvOK)
        } else {
            fmt.Println("send:", chosen)
        }
    }
}