使用异常 select 的意外行为

Unexpected behavior using an unusual select

我正在编写一些代码,将数据从一个通道传递到另一个通道。根据一些直觉和 我希望下面的代码可以工作(other 是一个足够大的缓冲通道,outsource 通道):

for {
    select {
    case other <- (<-out):
        log.Warn("C")
    }
}

确实如此!但是 其他情况根本不会触发 ,例如以下代码的日志中没有 Ds:

for {
    select {
    case other <- (<-out):
        log.Warn("C")
    default:
        log.Warn("D")
    }
}

使用更传统的解决方案,日志中有 Ds:

for {
    select {
    case msg := <-out:
        other <- msg
        log.Warn("C")
    default:
        log.Warn("D")
    }
}

显然,我将使用通常的解决方案,但我仍然不知道为什么不寻常的解决方案无法按预期工作。

我怀疑答案就在 The Go Memory Model 的某个地方,但我不太明白这种情况下到底发生了什么。

我整理了一些 playground,您可以在其中查看此行为:

提前感谢任何可以对此有所了解的人!

当你有这个:

ch := make(chan int, 10)
// ...

select {
case ch <- <-out:
    fmt.Println("C")
default:
    fmt.Println("D")
}

第一个case的通信op是ch <- something,其中something<-out。但是 something 首先被评估,然后才检查 case 的哪个通信操作可以继续。

因此<-out会根据需要阻塞,然后ch <- something检查是否可以继续。由于您使用了足够大的缓冲区,它始终可以在您的示例中继续进行,因此永远不会选择 default

Spec: Select statements:

Execution of a "select" statement proceeds in several steps:

  1. For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send statements are evaluated exactly once, in source order, upon entering the "select" statement. The result is a set of channels to receive from or send to, and the corresponding values to send. Any side effects in that evaluation will occur irrespective of which (if any) communication operation is selected to proceed. Expressions on the left-hand side of a RecvStmt with a short variable declaration or assignment are not yet evaluated.
  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.
  3. Unless the selected case is the default case, the respective communication operation is executed.
  4. If the selected case is a RecvStmt with a short variable declaration or an assignment, the left-hand side expressions are evaluated and the received value (or values) are assigned.
  5. The statement list of the selected case is executed.

如果降低 ch 的缓冲区,您会偶尔看到 D 打印在输出中(在 Go Playground 上尝试)。

ch := make(chan int, 2)