Select 语句的细微差别

rvi*_*cio 2 concurrency select go

我阅读了有关 select 语句及其执行步骤的信息,但我没有完全理解这里发生的情况。

我创建了两个扇入函数的示例(来自Go 并发模式演讲

第一个

select {
case value := <-g1:
    c <- value
case value := <-g2:
    c <- value
}
Run Code Online (Sandbox Code Playgroud)

按预期从每个通道打印(每个通道都有自己的计数器):

Bob  : 0
Alice: 0
Bob  : 1
Alice: 1
Bob  : 2
Alice: 2
Alice: 3
Alice: 4
Bob  : 3
Alice: 5
Run Code Online (Sandbox Code Playgroud)

第二个

select {
case c <- <-g1:
case c <- <-g2:
}
Run Code Online (Sandbox Code Playgroud)

它随机选择一个通道并丢弃另一个通道的值:

Bob  : 0
Alice: 1
Alice: 2
Alice: 3
Bob  : 4
Alice: 5
Bob  : 6
Alice: 7
Alice: 8
Bob  : 9
Run Code Online (Sandbox Code Playgroud)

更新:在写这个问题时,我认为第二个select 等于

var v string
select {
case v = <-g1:
case v = <-g2:
    c <- v
}
Run Code Online (Sandbox Code Playgroud)

但我错了,因为这个总是从第二个通道打印(正如 switch 语句所预期的那样,因为 select 语句中没有失败):

Bob  : 0
Bob  : 1
Bob  : 2
Bob  : 3
Bob  : 4
Bob  : 5
Bob  : 6
Bob  : 7
Bob  : 8
Bob  : 9
Run Code Online (Sandbox Code Playgroud)

有人明白为什么我的第二个例子创建一个序列吗?

谢谢,

Jam*_*dge 5

您的第二个 select 语句被解释为:

v1 := <-g1
v2 := <-g2
select {
case c <- v1:
case c <- v2:
}
Run Code Online (Sandbox Code Playgroud)

如语言规范中所述,执行语句时将预先评估每个发送运算符的 RHS:

“select”语句的执行分几个步骤进行:

  1. 对于语句中的所有情况,接收操作的通道操作数以及发送语句的通道和右侧表达式在输入“select”语句时按源顺序仅计算一次。结果是一组要接收或发送到的通道,以及要发送的相应值。无论选择哪个(如果有)通信操作来进行,该评估中的任何副作用都会发生。RecvStmt 左侧带有短变量声明或赋值的表达式尚未计算。
  2. 如果一个或多个通信可以继续进行,则通过统一的伪随机选择选择一个可以继续进行的通信。否则,如果存在默认情况,则选择该情况。如果不存在默认情况,则“select”语句将阻塞,直到至少其中一个通信可以继续进行。
  3. ...

因此,在步骤 (1) 中, 和<-g1<-g2将被评估,从每个通道接收值。如果还没有什么可接收的,这可能会阻塞。

在 (2) 处,我们等到准备好c发送值,然后随机选择要执行的 select 语句的一个分支:因为它们都在同一通道上等待,所以它们都准备好继续执行。

这解释了您所看到的值被丢弃的行为,以及您在值被发送到 时出现的不确定行为c

如果您想等待g1g2,您将需要使用您发现的第一种形式的声明。