Go channel 采用最快回应

5 阅读3分钟

有时候,一份数据可能同时从多个数据源获取。这些数据源将返回相同的数据。因为各种因素,这些数据源的回应速度参差不一,甚至某个特定数据源的多次回应速度之间也可能相差很大。

同时从多个数据源获取一份相同的数据可以有效保障低延迟。我们只需采用最快的回应并舍弃其它较慢回应。

e.g.

注意:如果有 N 个数据源,为了防止被舍弃的回应对应的协程永久阻塞,则传输数据用的通道必须为一个容量至少为 N-1 的缓冲通道。

func source(c chan<- int32) {
 ra, rb := rand.Int31(), rand.Intn(3)+1
 time.Sleep(time.Duration(rb) * time.Second)
 c <- ra
}

func main() {
 rand.Seed(time.Now().UnixNano())

 c := make(chan int325// 必须用一个缓冲通道
 for i := 0; i < cap(c); i++ {
  go source(c)
 }
 rnd := <-c // 只有第一个回应被使用了
 fmt.Println(rnd)
}

e.g.

也可以使用选择机制来实现“采用最快回应”用例。 每个数据源协程只需使用一个缓冲为1的通道并向其尝试发送回应数据即可。

func source(c chan<- int32) {
 ra, rb := rand.Int31(), rand.Intn(3)+1
 time.Sleep(time.Duration(rb) * time.Second)
 select {
 case c <- ra:
 default:
 }
}

func main() {
 rand.Seed(time.Now().UnixNano())

 c := make(chan int321// 此通道容量必须至少为1
 for i := 0; i < 5; i++ {
  go source(c)
 }
 rnd := <-c // 只采用第一个成功发送的回应数据
 fmt.Println(rnd)
}

e.g.

如果一个“采用最快回应”用例中的数据源的数量很少,比如两个或三个,我们可以让每个数据源使用一个单独的缓冲通道来回应数据,然后使用一个 select 代码块来同时接收这三个通道。

func source() <-chan int32 {
 c := make(chan int321// 必须为一个缓冲通道
 go func() {
  ra, rb := rand.Int31(), rand.Intn(3)+1
  time.Sleep(time.Duration(rb) * time.Second)
  c <- ra
 }()
 return c
}

func main() {
 rand.Seed(time.Now().UnixNano())

 var rnd int32
 select{
 case rnd = <-source():
 case rnd = <-source():
 case rnd = <-source():
 }
 fmt.Println(rnd)
}

e.g.

如果需要处理许多个chan 呢?或者是,chan 的数量在编译的时候是不定的,在运行的时候需要处理一个 slice of chan,这个时候,通过 reflect.Select 函数,你可以将一组运行时的 case 传入,当作参数执行。

Go 的 select 是伪随机的,它可以在执行的 case 中随机选择一个 case,并把选择的这个 case 的索引(chosen)返回,如果没有可用的 case 返回,会返回一个 bool 类型的返回值,这个返回值用来表示是否有 case 成功被选择。如果是 recv case,还会返回接收的元素。Select 的方法签名如下:

// Select executes a select operation described by the list of cases.
// Like the Go select statement, it blocks until at least one of the cases
// can proceed, makes a uniform pseudo-random choice,
// and then executes that case. It returns the index of the chosen case
// andif that case was a receive operation, the value received and a
// boolean indicating whether the value corresponds to a send on the channel
// (as opposed to a zero value received because the channel is closed).
// Select supports a maximum of 65536 cases.
func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool)
func source() <-chan int32 {
 c := make(chan int321// 必须为一个缓冲通道
 go func() {
  ra, rb := rand.Int31(), rand.Intn(3)+1
  time.Sleep(time.Duration(rb) * time.Second)
  c <- ra
 }()
 return c
}

func quickReplyReflect(chs ...<-chan int32) <-chan int32 {
 out := make(chan int32)
 go func() {
  defer close(out)
  // 构造SelectCase slice
  var cases []reflect.SelectCase
  for _, c := range chs {
   cases = append(cases, reflect.SelectCase{
    Dir:  reflect.SelectRecv,
    Chan: reflect.ValueOf(c),
   })
  }
  // 循环,从cases中选择一个可用的
  for len(cases) > 0 {
   i, v, ok := reflect.Select(cases)
   if !ok { // 此通道关闭并且它的缓冲队列中为空
    cases = append(cases[:i], cases[i+1:]...)
    continue
   }
   out <- int32(v.Int())
   return // 该例子中只取一个
  }
 }()
 return out
}

func main() {
 rand.Seed(time.Now().UnixNano())

 rnd := <-orReflect(source(), source(), source())
 fmt.Println(rnd)
}