SELECT语句是否保证通道选择的顺序?

huangapple go评论78阅读模式
英文:

Do select statements guarantee order of channel selection?

问题

根据这个答案,如果一个goroutine在两个通道上进行选择操作,是否保证通道的选择顺序与它们发送的顺序相同?我特别关注的是发送方是单线程的情况。

举个例子,是否保证这段代码始终产生相同的输出结果:

package main

import (
  "fmt"
  "time"
)

var x, y chan int

func worker() {
  for {
    select {
    case v := <- x:
      fmt.Println(v)
    case v := <- y:
      fmt.Println(v)
    }
  }
}

func main() {
  x = make(chan int)
  y = make(chan int)
  go worker()
  x <- 1
  y <- 2
  <- time.After(1 * time.Second)
}

我的实验似乎表明,对于无缓冲通道,如上所示,这是有保证的,但对于有缓冲通道,这是不确定的。请问有人可以确认一下吗?

英文:

Following on from this answer, if a goroutine is selecting on two channels, is it guaranteed that the channels are selected in the same order that they are sent on? I'm particularly interested in the case where the sender is single threaded.

As an example, is it guaranteed that this code always produces the same output:

package main

import (
  &quot;fmt&quot;
  &quot;time&quot;
)

var x, y chan int

func worker() {
  for {
    select {
    case v := &lt;- x:
      fmt.Println(v)
    case v := &lt;- y:
      fmt.Println(v)
    }
  }
}

func main() {
  x = make(chan int)
  y = make(chan int)
  go worker()
  x &lt;- 1
  y &lt;- 2
  &lt;- time.After(1 * time.Second)
}

My experiments would seem to indicate that this is guaranteed for unbuffered channels, as shown, but not guaranteed for buffered channels. Can someone confirm that please?

答案1

得分: 9

不,这并不是保证的。实际上,它是随机选择的。根据Go语言规范(https://golang.org/ref/spec#Select_statements)(强调添加):

  1. 如果有一个或多个通信可以进行,将通过均匀的伪随机选择选择一个可以进行的通信。否则,如果有一个默认情况,将选择该情况。如果没有默认情况,"select"语句将阻塞,直到至少有一个通信可以进行。
英文:

No. It's not guaranteed. In fact, it's randomized. From the Go language spec (emphasis added):

> 2. If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the "select" statement blocks until at least one of the communications can proceed.

答案2

得分: 7

虽然在你的示例中,可以保证先打印出1,但这并不是因为select的工作方式。

这是因为x是一个无缓冲通道,对它的发送操作会阻塞,直到有其他协程从中接收数据,然后才能发送到通道y

因此,在worker()函数内部的select语句不会同时看到两个准备好的通道。

如果可以同时看到两个准备好的通道,那么其中一个会被伪随机地选择。详细信息请参见https://stackoverflow.com/questions/47645808/how-does-select-work-when-multiple-channels-are-involved/47648910#47648910

因此,请注意,如果你使用带缓冲的通道:

x = make(chan int, 1)
y = make(chan int, 1)

那么对xy的发送操作将不会阻塞,现在由协程调度器决定协程的执行顺序。在worker()协程到达并评估通道操作之前,主协程有可能先发送两个值,因此你可能会看到先打印出1然后是2,或者先打印出2然后是1

英文:

Although in your example it's guaranteed that you'll see 1 printed first, but not because of how select works.

It's because x is an unbuffered channel, and send on it will block until someone receives from it, and only then can you send on channel y.

So your select inside worker() won't see 2 ready channels at the same time.

It if would, then one is chosen pseudo-randomly. For details, see https://stackoverflow.com/questions/47645808/how-does-select-work-when-multiple-channels-are-involved/47648910#47648910

So note that if you'd use buffered channels:

x = make(chan int, 1)
y = make(chan int, 1)

Then send on x and y wouldn't block, and now it's up to the goroutine scheduler how goroutines are executed. It's possible the main goroutine can send both values before the worker() goroutine reaches and evaluates the channel operations, and so you could see 1 then 2 printed, just as well as 2 and then 1.

huangapple
  • 本文由 发表于 2021年8月4日 19:27:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/68650423.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定