【问题标题】:Golang channel output orderGolang 通道输出顺序
【发布时间】:2018-11-12 06:05:45
【问题描述】:
func main() {
  messages := make(chan string)
  go func() { messages <- "hello" }()
  go func() { messages <- "ping" }()
  msg := <-messages
  msg2 := <-messages
  fmt.Println(msg)
  fmt.Println(msg2)

上面的代码在我的终端上始终打印“ping”,然后打印“hello”。 我对此打印的顺序感到困惑,所以我想知道是否可以澄清我的想法。

我了解无缓冲通道在等待发送方和接收方时会阻塞。因此,在上述情况下,当执行这两个 go 例程时,在这两种情况下都没有接收器。所以我猜这两个例程都会阻塞,直到通道上有接收器。

现在...我假设第一个“hello”尝试进入频道,但必须等待...同时,“ping”尝试,但又必须等待。那么

msg := <- messages

出现了,所以我假设在那个阶段,程序将任意选择一个等待的 goroutine 并允许它将消息发送到通道中,因为 msg 已准备好接收。

但是,似乎无论我运行多少次程序,总是被分配“ping”的 msg 和分配“hello”的 msg2,这给人的印象是“ping”总是优先发送首先(给味精)。这是为什么呢?

【问题讨论】:

  • 你想要什么。你想同步频道以便hello总是先打印吗?
  • 您的观察是正确的,线程的执行顺序(在 golang 的情况下,go 例程)并非设计为可预测的。如果您想以特定方式控制执行流程,也许这篇文章 (stackoverflow.com/questions/39818254/…) 可能会对您有所帮助。

标签: go channel


【解决方案1】:

当我第一次遇到这个时,我很困惑。但现在我很清楚这一点。原因不是通道,而是 goroutine。

正如The Go Memory Model 提到的,goroutine 的运行和退出并不能保证,所以当你创建两个 goroutine 时,你不能确保它们按顺序运行。

所以如果你想打印遵循先进先出规则,你可以像这样改变你的代码:

func main() {
    messages := make(chan string)
    go func() {
        messages <- "hello"
        messages <- "ping"
    }()
    //go func() { messages <- "ping" }()
    msg := <-messages
    msg2 := <-messages
    fmt.Println(msg)
    fmt.Println(msg2)
}

【讨论】:

    【解决方案2】:

    我刚刚经历了同样的事情。在这里查看我的帖子:Golang channels, order of execution

    和你一样,我看到了一种违反直觉的模式。在一个实际上不应该有模式的地方。一旦你启动了一个 go 进程,你就启动了一个执行线程,基本上所有关于线程将执行其步骤的顺序的赌注都在那个时候被取消了。但是如果有一个命令,逻辑告诉我们第一个被调用的将首先被执行。

    实际上,如果你每次都重新编译那个程序,结果会有所不同。这就是我开始在本地计算机上编译/运行它时发现的。为了使结果随机,我不得不“弄脏”文件,例如添加和删除一个空格。然后编译器会重新编译程序,然后我会得到一个随机的执行顺序。但是在 go 沙箱中编译时,结果总是一样的。

    当您使用沙盒时,结果显然会被缓存。通过使用微不足道的更改,我无法在沙箱中获得更改顺序。我改变它的唯一方法是在 go 语句的启动之间发出 time.Sleep(1) 命令。然后,第一个启动的将是每次执行的第一个。我仍然不认为我会赌上这种情况会继续发生,因为它们是独立的执行线程并且没有任何保证。

    最重要的是,我看到了一个确定性的结果,而应该没有确定性。这就是困扰我的原因。当我发现结果在正常环境中确实是随机的时,我完全清醒了。沙盒是一个很棒的工具。但这不是一个正常的环境。在本地编译并运行您的代码,您将看到您所期望的不同结果。

    【讨论】:

      【解决方案3】:

      在 Golang Spec Channels order 中被描述为:-

      通道充当先进先出队列。例如,如果一个 goroutine 在通道上发送值,第二个 goroutine 接收 它们的值是按照发送的顺序接收的。

      它将打印出哪个值可以首先在另一端接收。 如果你想同步他们使用不同的频道或添加wait Groups

      package main
      
      import (
          "fmt"
      )
      
      func main() {
        messages1 := make(chan string)
        messages2 := make(chan string)
        go func(<-chan string) {
              messages2 <- "ping" 
          }(messages2)
        go func(<-chan string) {
              messages1 <- "hello" 
          }(messages1)
        fmt.Println(<-messages1)
        fmt.Println(<-messages2)
      }
      

      如果您看到,您可以根据您的选择使用不同的渠道轻松获得您想要的任何价值。

      Go playground

      【讨论】:

      • “它不依赖于先在通道上发送的值。它会打印出哪个值可以先在另一端接收”为假。这些值总是按照它们发送的顺序被接收。 “容量为 C 的通道上的第 k 次接收发生在该通道的第 k+C 次发送完成之前。” golang.org/ref/mem#tmp_7最先可用的值最先发送的值。
      • 好的,为什么先发送的值不是先打印。其实我也想知道。因为看了很多回答,大家都说没关系。好吧,谢谢我已经编辑了我的答案,还查看了您提供的链接。
      • @Himanshu 正如 Alexander Trakhimenok 在他的回答中指出的那样,顺序取决于 goroutines 的调度和执行方式。据我所知,这是一个实现细节。您不应该期望您的 goroutine 以您在源代码中编写它们的相同顺序执行。例如。 go a(); go b() 这里的 Go 规范不保证 a 将在 b 之前运行,也许会,但也许不会。这取决于调度程序。如果b 首先运行,您将获得 OP 看到的结果。 msg := &lt;-messages 将导致 "ping"msg2 := &lt;-messages"hello"
      • 是的,我从许多关于围棋例程的指控中知道这些事情。但是为什么 OP 得到相同的答案呢?好吧,我刚刚给出了答案,以防 OP 想要控制首先打印哪条消息。
      【解决方案4】:

      这不是关于读取通道的顺序,而是关于 goroutines 执行的顺序,这是不能保证的。

      尝试从您正在写入通道的函数中“Println”(写入之前和之后),我认为它应该与从通道中读取的顺序相同。

      【讨论】:

        猜你喜欢
        • 2019-01-30
        • 1970-01-01
        • 2017-05-14
        • 2017-01-03
        • 2020-08-09
        • 1970-01-01
        • 2022-01-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多