停止循环的代码不起作用

Code for stopping a loop doesn't work

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

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

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 之后就崩溃了? quitroutine 仅在 cnt == 5 时写入 quit 通道,但不会自行终止。而 runLoop,如果它在 quit 通道上接收,应该只打印 "quit!"( 而不是 ),但不会自行终止。

  2. 为什么我没有得到 "quit!" 输出?我能得到 quit 频道吗?

  3. 这个需要如何正确实现

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

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

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

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

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

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

  2. 你永远不会得到 "quit!" 输出,因为那个 goroutine 被卡住了试图做 send <-cnt.

  3. 我看到的最简单的解决方案是调整 runLoop() 以便 send <- cnt 成为 select.

    [= 的情况50=]

我会把 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 通道获取输入。