英文:
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
cnt
is 5?quitroutine
only writes to thequit
channel ifcnt == 5
, but doesn't terminate itself. WhilerunLoop
, if it receives on thequit
channel, 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
quit
channel? -
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 == 5
thequitroutine
begins attempting to send onquit
. Becausequit <- struct{}{}
is not aselect
case, 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 <- cnt
is 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).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论