如何确定从通道接收的顺序?

How one can deterime the order that the order of receiving from channels?

考虑以下来自 the tour of go 的示例。

如何确定频道的接收顺序? 为什么 x 总是从 gorouting 获得第一个输出? 这听起来很合理,但我没有找到任何关于它的文档。 我尝试添加一些睡眠,但仍然 x 从第一次执行的 gorouting 中获取输入。

    c := make(chan int)
    go sumSleep(s[:len(s)/2], c)
    go sum(s[len(s)/2:], c)
    x, y := <-c, <-c // receive from c

    fmt.Println(x, y, x+y)

睡眠是发送到频道之前。

消息总是按发送顺序接收。那是确定性的。

然而,跨并发 Goroutines 的任何给定操作的执行顺序是不是确定性的。所以如果你有两个 goroutines 同时在一个通道上发送,你无法知道哪个先发送,哪个再发送。如果您有两个 goroutines 在同一个频道上接收,则相同。

I tried to add some sleep and still x get the input from the first executed goroutine

除了@Adrian 写的内容之外,在您的代码中 x 将始终从 c 的第一个 recieve 获得结果,因为元组分配的语言规则

The assignment proceeds in two phases. First, the operands of index expressions and pointer indirections (including implicit pointer indirections in selectors) on the left and the expressions on the right are all evaluated in the usual order. Second, the assignments are carried out in left-to-right order.

补充一点 :我们不知道这两个 goroutines 可能 运行 的顺序。如果你的睡眠发生在你的通道发送之前,并且睡眠 "long enough",1 这将保证 other goroutine 可以 运行 发送的重点。如果两个 goroutines 运行 "at the same time" 并且都不等待(如在原始的 Tour 示例中),我们无法确定哪个 goroutine 会首先到达其 c <- sum 行。

运行 Go Playground 上的 Tour 示例(直接或通过 Tour 网站),我实际上得到:

-5 17 12

在输出 window 中(因为我们知道 -9 在切片的第二半)告诉我们 second goroutine "got there" 首先(到通道发送)。从某种意义上说,这只是运气——但是当使用 Go Playground 时,所有作业都是 运行 在相当确定的环境中,具有单个 CPU 和协作调度,因此结果更可预测。换句话说,如果第二个 goroutine 在 运行 上第一个到达那里,它可能会在下一个。如果 playground 使用多个 CPUs and/or 一个不太确定的环境,结果可能会从一个 运行 变为下一个,但不能保证。

无论如何,假设您的代码按照您说的去做(我相信它会做),这:

go sumSleep(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)

让第一个发件人等待,第二个发件人 运行 先等。但这是我们已经观察到的当我们让两个例程竞争时实际发生的情况。要看到 变化,我们需要让 发送者延迟。

我在 Go Playground here 中制作了示例的修改版本,打印了更多注释。在第二半总和中插入延迟后,我们将第一半总和视为 x:

2nd half: sleeping for 1s
1st half: sleeping for 0s
1st half: sending 17
2nd half: sending -5
17 -5 12

正如我们预期的那样,因为一秒是 "long enough"。


1"long enough"多长时间?好吧,这取决于:我们的计算机有多快?他们还做了多少其他事情?如果计算机足够快,几毫秒甚至几纳秒的延迟可能就足够了。如果我们的计算机真的很旧或者忙于其他更高优先级的任务,几毫秒的时间可能不够。如果问题足够大,一秒钟可能不够用。如果您可以通过某种 同步 操作更好地控制它,那么选择特定数量的 time 通常是不明智的,通常您可以做到。例如,使用 sync.WaitGroup 变量允许您等待 n 个 goroutines(对于 n 的一些 运行time 值)在您自己的 goroutine 继续之前调用 Done 函数。


Playground 代码,为方便起见复制到 Whosebug

package main

import (
    "fmt"
    "time"
)

func sum(s []int, c chan int, printme string, delay time.Duration) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    fmt.Printf("%s: sleeping for %v\n", printme, delay)
    time.Sleep(delay)
    fmt.Printf("%s: sending %d\n", printme, sum)
    c <- sum
}

func main() {
    s := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int)
    go sum(s[:len(s)/2], c, "1st half", 0*time.Second)
    go sum(s[len(s)/2:], c, "2nd half", 1*time.Second)
    x, y := <-c, <-c

    fmt.Println(x, y, x+y)
}