英文:
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
问题:
-
为什么在
cnt等于5之后会崩溃?quitroutine只在cnt == 5时向quit通道写入,但它本身并不终止。而runLoop如果在quit通道上接收到消息,应该只打印"quit!"(但实际上没有打印),而不是终止自己。 -
为什么我没有得到"quit!"的输出?我是否正确使用了
quit通道? -
如何正确实现这个功能?
英文:
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 (
"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")
}
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:
-
Why does it crash at all after
cntis 5?quitroutineonly writes to thequitchannel ifcnt == 5, but doesn't terminate itself. WhilerunLoop, if it receives on thequitchannel, should just print "quit!" (which it doesn't), but not terminate itself. -
Why don't I get the "quit!" output? Do I even get the
quitchannel? -
How does this need to be implemented correctly
答案1
得分: 2
正如Adrian所说,你的其中一个goroutine正在尝试在quit上发送数据,而另一个goroutine正在尝试在send上发送数据。回答你的问题:
-
当
cnt == 5时,quitroutine开始尝试在quit上发送数据。因为quit <- struct{}{}不是select语句的一个case,该goroutine将被阻塞,直到另一个goroutine尝试从quit上读取数据。另一个goroutine同样被阻塞,尝试执行send <- cnt(当cnt = 6时)。 -
你没有看到"quit!"的输出,因为那个goroutine被阻塞在尝试执行
send <- cnt。 -
我看到的最简单的解决方案是调整
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:
-
When
cnt == 5thequitroutinebegins attempting to send onquit. Becausequit <- struct{}{}is not aselectcase, the goroutine will block until another tries to read fromquit. The other goroutine is similarly stuck trying to dosend <- cnt(whencnt = 6). -
You never get the "quit!" output because that goroutine is stuck trying to do
send <-cnt. -
The simplest solution I see would be to adjust
runLoop()so that thesend <- cntis a case in theselect.
I'd change runLoop() to look like this:
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
}
}
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).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论