【问题标题】:Code for stopping a loop doesn't work停止循环的代码不起作用
【发布时间】:2017-09-08 21:39:26
【问题描述】:

我正在尝试在 Go 中实现停止循环。 我现在拥有的代码的灵感来自这里: how to kill goroutine

但是,我无法让我的代码按预期运行。我的复杂代码库中的代码的简化版本是这样的:

package main

import (
    "fmt"
    "time"
)

var quit chan struct{}

var send chan int

func quitroutine() {
    for {
        select {
        case cnt := <-send:
            fmt.Println(cnt)
            if cnt == 5 {
                quit <- struct{}{}
            }
        }
    }
}

func runLoop() {
    cnt := 0
    for {
        select {
        case <-quit:
            fmt.Println("quit!")
        default:
            fmt.Println("default")
        }
        fmt.Println("inloop")
        time.Sleep(1 * time.Second)
        cnt++
        send <- cnt
    }
}

func main() {
    quit = make(chan struct{})
    send = make(chan int)
    go quitroutine()
    runLoop()
    fmt.Println("terminated")
}

此代码崩溃:

default
inloop
5
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.runLoop()
    /tmp/t.go:37 +0x1a6
main.main()
    /tmp/t.go:45 +0xa4

goroutine 5 [chan send]:
main.quitroutine()
    /tmp/t.go:18 +0x10e
created by main.main
    /tmp/t.go:44 +0x9f
exit status 2

问题:

  1. 为什么在cnt 为 5 之后它会崩溃?如果cnt == 5quitroutine 仅写入quitchannel,但不会自行终止。而runLoop,如果它在quit 频道上接收,应该只打印“退出!” (它不会),但不会自行终止。

  2. 为什么我没有得到“退出!”输出?我还能得到quit 频道吗?

  3. 这需要如何正确实现

【问题讨论】:

  • 你不是任何 breakreturn 语句,这会让你摆脱循环。
  • @Flimzy 在main(),我正在执行go quitroutine()。我的假设是在quitroutine()我维护一个计数器,当计数器达到5时,它发送到quit以停止runLoop(),然后将终止整个程序。
  • @RayfenWindspear 我之前在quit 的情况下有return(和break),但出于调试目的我删除了它

标签: go channel


【解决方案1】:

正如 Adrian 所说,您的一个 goroutine 正在尝试发送 quit,而另一个正在尝试发送 send。回答您的问题:

  1. cnt == 5 quitroutine 开始尝试在quit 上发送。因为quit &lt;- struct{}{} 不是select 的情况,所以goroutine 将阻塞,直到另一个尝试从quit 读取。另一个 goroutine 也同样被困在尝试执行 send &lt;- cnt(当 cnt = 6 时)。

  2. 你永远不会得到“退出!”输出,因为该 goroutine 卡在尝试执行 send &lt;-cnt

  3. 我看到的最简单的解决方案是调整runLoop(),使send &lt;- cnt 成为select 中的一个案例。

我会将runLoop() 更改为如下所示:

func runLoop() {
    cnt := 0
    for {
        select {
        case <-quit:
            fmt.Println("quit!")
        case send <- cnt: // moved stuff here
            fmt.Println("inloop")
            time.Sleep(1 * time.Second)
            cnt++
        default:
            fmt.Println("default")
        }
        // stuff used to be here
    }
}

这给了我输出(直到我杀死了程序):

default
inloop
0
inloop
1
inloop
2
inloop
3
inloop
4
inloop
5
quit!
default
inloop
6
inloop
7

这似乎主要是你所追求的。

我还要注意quitroutine() 中的select 块是不必要的,因为它只有一种情况。清理它可能会更清楚地表明该 goroutine 在尝试发送时被卡住,并且永远不会从 send 通道中获取输入。

【讨论】:

    【解决方案2】:

    当您尝试在 quit 通道上发送时,quitroutine 会阻塞,直到从中读取到内容。

    与此同时,runloop 中的主程序正尝试在send 频道上发送下一个号码。这也会阻塞,因为正在读取它的例程当前在尝试在 quit 通道上发送时被阻塞。

    两个例程都被阻塞了,这是一个死锁,所以程序崩溃了。

    这可以通过将一个或两个通道发送到select 中来解决,或者使一个或两个通道缓冲(即使缓冲长度为 1 也足够了)。

    【讨论】:

      猜你喜欢
      • 2015-07-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-09
      • 1970-01-01
      • 2016-09-02
      • 1970-01-01
      • 2014-12-06
      相关资源
      最近更新 更多