【问题标题】:Why does this select always run the default case when the first case actually is executed?为什么实际执行第一个案例时,此选择始终运行默认案例?
【发布时间】:2018-01-08 19:53:16
【问题描述】:

我正在努力更好地了解 golang 频道。在阅读this article 时,我正在玩弄非阻塞发送并想出了以下代码:

package main
import (
    "fmt"
    "time"
)

func main() {
    stuff := make(chan int)
    go func(){
        for i := 0; i < 5; i ++{
            select {
            case stuff <- i:
                fmt.Printf("Sent %v\n", i)
            default:
                fmt.Printf("Default on %v\n", i)
            }
        }
        println("Closing")
        close(stuff)
    }()
    time.Sleep(time.Second)
    fmt.Println(<-stuff)
    fmt.Println(<-stuff)
    fmt.Println(<-stuff)
    fmt.Println(<-stuff)
    fmt.Println(<-stuff)
}

这将打印:

Default on 0
Default on 1
Default on 2
Default on 3
Default on 4
Closing
0
0
0
0
0

虽然我明白只有0s 会被打印出来,但我真的不明白为什么第一次发送仍然会触发选择的default 分支?

在这种情况下,选择行为背后的逻辑是什么?

Example at the Go Playground

【问题讨论】:

    标签: select go channels


    【解决方案1】:

    它只执行默认情况,因为 for 循环在开始从通道读取任何内容之前运行了 5 次。每次通过时,由于无法从通道读取任何内容,因此进入默认情况。如果可以从通道读取某些内容,它将执行该案例。

    【讨论】:

      【解决方案2】:

      您永远不会向stuff 发送任何值,您会在进行fmt.Println 语句中的任何接收操作之前执行所有默认情况。 default 的情况会在没有其他操作可以继续时立即执行,这意味着您的循环将尽快执行并返回。

      您想阻止循环,因此您不需要 default 案例。您也不需要最后的close,因为您不依赖封闭的通道来解除接收或中断range 子句。

      stuff := make(chan int)
      go func() {
          for i := 0; i < 5; i++ {
              select {
              case stuff <- i:
                  fmt.Printf("Sent %v\n", i)
              }
          }
          println("Closing")
      }()
      time.Sleep(time.Second)
      fmt.Println(<-stuff)
      fmt.Println(<-stuff)
      fmt.Println(<-stuff)
      fmt.Println(<-stuff)
      fmt.Println(<-stuff)
      

      https://play.golang.org/p/k2rmRDP38f

      还要注意最后的“Sent”和“Closing”行没有被打印出来,因为你没有其他同步等待goroutine完成,但是这不会影响这个例子的结果。

      【讨论】:

      • 所以我的示例打印的0int 的零值,而不是我认为我会发送的值?
      • @m90:是的,无缓冲通道已经关闭,所以没有收到任何东西。
      【解决方案3】:

      您的第一个案例没有执行。

      这是您的程序的作用:

      1. 启动一个 goroutine。
      2. 尝试在频道上通过4发送0,都被阻塞了,因为没有任何东西读取频道,所以使用默认值。
      3. 同时,在主 goroutine 中,您正在休眠一秒钟...
      4. 然后在第二秒过去后,尝试从通道读取,但它已关闭,因此您每次都会收到0

      要获得所需的行为,您有两种选择:

      1. 使用缓冲通道,它可以保存您发送的所有数据:

        stuff := make(chan int, 5)
        
      2. 不要在你的选择语句中使用default,这会导致每次发送都等到成功。

      哪个是首选取决于您的目标。对于像这样的最小示例,可能没有更好或更差。

      【讨论】:

      【解决方案4】:

      由于您使用的是非阻塞“发送”,stuff &lt;- i 只会在通道上已经有阅读器等待读取内容或通道有缓冲区时才会执行。如果没有,“发送”将不得不阻塞。

      现在,由于您在从频道读取的打印语句之前有一个time.Sleep(time.Second),因此在 1 秒过去之前,该频道没有阅读器。另一方面,goroutine 在这段时间内完成执行并且不发送任何内容。

      您在输出中看到全为零​​,因为 fmt.Println(...) 语句正在从关闭的通道中读取。

      【讨论】:

      • +1 是唯一一个非常明确地指出需要有读者已经在等待交接的人。其他人暗示了这一点,但实际上并没有这样解释,以免被误解。
      • @Ray 错了。发送是阻塞的,但选择是非阻塞的。巨大的差异。
      • @PleaseHelp 呃,除非我没有正确理解您的评论,否则我认为您在本问答的上下文中混淆了“发送”一词。这里的“发送”一词包括select 语句和其中的所有内容。正是 selectdefault 使 stuff &lt;- i 成为“非阻塞发送”。
      • 我的意思是在本 Q/A 的上下文中使用“发送”一词具有误导性。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-05
      • 1970-01-01
      • 2017-02-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多