停止循环的代码不起作用

huangapple go评论82阅读模式
英文:

Code for stopping a loop doesn't work

问题

我正在尝试在Go中实现循环停止。我目前的代码灵感来自这里:https://stackoverflow.com/questions/42560109/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之后会崩溃?quitroutine只在cnt == 5时向quit通道写入,但它本身并不终止。而runLoop如果在quit通道上接收到消息,应该只打印"quit!"(但实际上没有打印),而不是终止自己。

  2. 为什么我没有得到"quit!"的输出?我是否正确使用了quit通道?

  3. 如何正确实现这个功能?

英文:

I am trying to implement a stop to a loop in Go.
The inspiration from the code I have right now is from here:
https://stackoverflow.com/questions/42560109/how-to-kill-goroutine#

However, I've not been able to get my code to behave as expected. The simplified version of my code from a complex code base is this:

package main

import (
	&quot;fmt&quot;
	&quot;time&quot;
)

var quit chan struct{}

var send chan int

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

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

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

This code crashes:

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

Questions:

  1. Why does it crash at all after cnt is 5? quitroutine only writes to the quitchannel if cnt == 5, but doesn't terminate itself. While runLoop, if it receives on the quit channel, should just print "quit!" (which it doesn't), but not terminate itself.

  2. Why don't I get the "quit!" output? Do I even get the quit channel?

  3. How does this need to be implemented correctly

答案1

得分: 2

正如Adrian所说,你的其中一个goroutine正在尝试在quit上发送数据,而另一个goroutine正在尝试在send上发送数据。回答你的问题:

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

  2. 你没有看到"quit!"的输出,因为那个goroutine被阻塞在尝试执行send <- cnt

  3. 我看到的最简单的解决方案是调整runLoop()函数,使得send <- cnt成为select语句的一个case。

我会将runLoop()修改为以下代码:

func runLoop() {
    cnt := 0
    for {
        select {
        case <-quit:
            fmt.Println("quit!")
        case send <- cnt: // 将代码移到这里
            fmt.Println("inloop")
            time.Sleep(1 * time.Second)
            cnt++
        default:
            fmt.Println("default")
        }
        // 之前的代码在这里
    }
}

这样我得到的输出(直到我终止程序)是:

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

这似乎基本符合你的要求。

我还想指出,在quitroutine()函数中的select语句是不必要的,因为它只有一个case。清理这部分代码可能会更清楚地表明该goroutine被阻塞在尝试发送数据,而且从send通道中不会接收任何输入。

英文:

Just as Adrian has said, one of your goroutines is trying to send on quit, while the other is trying to send on send. To answer your questions:

  1. When cnt == 5 the quitroutine begins attempting to send on quit. Because quit &lt;- struct{}{} is not a select case, the goroutine will block until another tries to read from quit. The other goroutine is similarly stuck trying to do send &lt;- cnt (when cnt = 6).

  2. You never get the "quit!" output because that goroutine is stuck trying to do send &lt;-cnt.

  3. The simplest solution I see would be to adjust runLoop() so that the send &lt;- cnt is a case in the select.

I'd change runLoop() to look like this:

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

This gives me output (until I killed the program):

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

Which seems to mostly be what you were after.

I'd also like to note that the select block in quitroutine() is unnecessary because it only has one case. Cleaning that up may make it more clear that that goroutine is stuck trying to send, and never takes the input from the send channel.

答案2

得分: 1

当你尝试在quit通道上发送消息时,quitroutine会阻塞直到有其他地方从该通道读取。

同时,主程序中的runloop正在尝试在send通道上发送下一个数字。这也会导致阻塞,因为负责从该通道读取的例程当前正阻塞在尝试在quit通道上发送消息。

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

可以通过将一个或两个通道的发送操作放在select语句中,或者将一个或两个通道设置为带缓冲区(即使缓冲区长度为1也足够)来解决这个问题。

英文:

When you try to send on the quit channel, the quitroutine blocks until something reads from it.

At the same time, the main routine, in runloop, is trying to send the next number on the send channel. This also blocks, because the routine that would be reading from it is currently blocked trying to send on the quit channel.

Both routines are blocked, which is a deadlock, so the program crashes.

This could be fixed by either putting one or both channel send in a select, or making one or both channels buffered (even a buffer length of 1 would suffice).

huangapple
  • 本文由 发表于 2017年9月8日 03:04:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/46103456.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定