【问题标题】:Break out of select loop?跳出选择循环?
【发布时间】:2014-10-17 15:29:46
【问题描述】:

我正在尝试在循环中使用select 来接收消息或超时信号。如果收到超时信号,循环应该中止:

package main
import ("fmt"; "time")
func main() {
    done := time.After(1*time.Millisecond)
    numbers := make(chan int)
    go func() {for n:=0;; {numbers <- n; n++}}()
    for {
        select {
            case <-done:
                break
            case num := <- numbers:
                fmt.Println(num)
        }
    }
}

但是,它似乎并没有停止:

$ go run a.go
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[...]
3824
3825
[...]

为什么?我是不是用错了time.After

【问题讨论】:

  • 如果你一直在等待已经发生的事情,你永远不会超时。

标签: select concurrency go goroutine


【解决方案1】:

围棋spec says:

“break”语句终止最里面的“for”的执行, 同一函数中的“switch”或“select”语句。

在您的示例中,您只是脱离了 select 语句。如果您将 break 替换为 return 语句,您将看到它正在工作。

【讨论】:

  • 但是为什么breakselect/switch 中做任何事情?我以为他们已经默认爆发了,这就是为什么有 fallthrough 关键字?
  • select 语句不是 switch 语句。 fallthrough 仅适用于 switch 语句,请参阅 the spec
  • 这可能会让人感到困惑,因为人们有时会在 select 语句中提到“失败”,但那是完全不同的东西。当你有一个默认子句使 select 语句非阻塞时。
  • 当你想退出一个更大的 case 块时,你可能想使用一个 break 语句。 (哈哈,我希望我没有引起更多的混乱......)
  • 您可以使用ifgoto 或另一个switch 退出case 子句,但是可以吗?所以我想每次我有一个从多个渠道接收多条消息的进程时,我都需要像 Martin 的回答那样使用带标签的中断?
【解决方案2】:

这种情况的“Go”方式是使用标签并在标签上打断,例如:

L:
    for {
        select {
            case <-done:
                break L
            case num := <- numbers:
                fmt.Println(num)
        }
    }

参考:

【讨论】:

    【解决方案3】:

    在您的示例代码中,return 似乎像 Pat 所说的那样合适,但为了将来参考,您可以使用标签:

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        done := time.After(1 * time.Millisecond)
        numbers := make(chan int)
    
        // Send to channel
        go func() {
            for n := 0; ; {
                numbers <- n
                n++
            }
        }()
    
    readChannel:
        for {
            select {
            case <-done:
                break readChannel
            case num := <-numbers:
                fmt.Println(num)
            }
        }
    
        // Additional logic...
        fmt.Println("Howdy")
    }
    

    【讨论】:

    • 我添加了一个减号,因为循环的标签比我所知道的任何其他事情(包括我认为的全局变量)都更会损害代码的可读性。我认为应该不惜一切代价避免它们。
    • @DmitryVerhoturov 这是货物崇拜的废话。 Go 包含标签和 goto 是有原因的(并排除了许多其他更“流行”的功能)。如果您需要使用标签从 select 语句内部跳出循环是一个合理的解决方案。你甚至没有提供替代方案(我想是在选择中设置一个局部变量并在外面检查它以执行中断)。
    • 一个更易读的替代方案就在拐角处,它是separating a loop to separate function,里面有returns。 Return 比break label 更具可读性。如果它是“货物崇拜”,请提供一个来自真实代码的示例,其中带有 return 的单独函数在删除该函数并引入标签后将变得更容易理解:我想不出任何 Go 代码中的一个我到目前为止,我看到了许多可以通过引入函数来改进的地方。
    【解决方案4】:

    我有以下解决方案,使用匿名函数。

        func() {
        for {
            select {
            case <-time.After(5 * time.Second):
                if token := c.Connect(); token.Wait() && token.Error() != nil {
                    fmt.Println("connect err:", token.Error())
                } else {
                    fmt.Println("breaking")
                    return
                }
            }
        }
        }()
    

    【讨论】:

      【解决方案5】:

      使用一些控制变量来跳过循环怎么样?打破标签有时很难或不太容易理解。

      package main
      import ("fmt"; "time")
      func main() {
          done := time.After(1*time.Millisecond)
          numbers := make(chan int)
          go func() {for n:=0;; {numbers <- n; n++}}()
          completed := false
          for !completed {
              select {
                  case <-done:
                      completed = true
                      // no break needed here
                  case num := <- numbers:
                      fmt.Println(num)
              }
          }
      }
      

      【讨论】:

      • 谢谢!这就是如此简单优雅
      猜你喜欢
      • 1970-01-01
      • 2021-08-31
      • 2012-01-16
      • 2020-05-21
      • 2015-04-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多