英文:
How can I stop the goroutine based on the returned value from that goroutine
问题
这里我创建了一个 Go Playground 的示例:sGgxEh40ev,但无法使其正常工作。
quit := make(chan bool)
res := make(chan int)
go func() {
idx := 0
for {
select {
case <-quit:
fmt.Println("检测到退出信号!")
return
default:
fmt.Println("goroutine 正在执行任务..")
res <- idx
idx++
}
}
}()
for r := range res {
if r == 6 {
quit <- true
}
fmt.Println("我收到了:", r)
}
输出结果:
goroutine 正在执行任务..
goroutine 正在执行任务..
我收到了: 0
我收到了: 1
goroutine 正在执行任务..
goroutine 正在执行任务..
我收到了: 2
我收到了: 3
goroutine 正在执行任务..
goroutine 正在执行任务..
我收到了: 4
我收到了: 5
goroutine 正在执行任务..
goroutine 正在执行任务..
致命错误:所有的 goroutine 都处于休眠状态 - 死锁!
这种情况是否可能?我哪里错了?
[1]: https://play.golang.org/p/sGgxEh40ev
请注意,由于我是一个文本模型,无法运行代码。我只能帮助你翻译和理解代码的含义。如果你在代码中遇到问题,建议你在相关的开发者社区或论坛上寻求帮助,这样你可以得到更专业的解答。
英文:
Like here I created a go playground sample: sGgxEh40ev, but cannot get it work.
quit := make(chan bool)
res := make(chan int)
go func() {
idx := 0
for {
select {
case <-quit:
fmt.Println("Detected quit signal!")
return
default:
fmt.Println("goroutine is doing stuff..")
res <- idx
idx++
}
}
}()
for r := range res {
if r == 6 {
quit <- true
}
fmt.Println("I received: ", r)
}
Output:
goroutine is doing stuff..
goroutine is doing stuff..
I received: 0
I received: 1
goroutine is doing stuff..
goroutine is doing stuff..
I received: 2
I received: 3
goroutine is doing stuff..
goroutine is doing stuff..
I received: 4
I received: 5
goroutine is doing stuff..
goroutine is doing stuff..
fatal error: all goroutines are asleep - deadlock!
Is this possible? Where am I wrong
答案1
得分: 7
问题在于在goroutine中,你使用select
来检查是否应该中止,但是你使用default
分支来执行其他工作。
如果没有通信(在case
分支中列出)可以进行,那么将执行default
分支。因此,在每次迭代中,都会检查quit
通道,但是如果无法从中接收(还不需要退出),则会执行default
分支,该分支无条件地尝试在res
上发送一个值。现在,如果主goroutine没有准备好从中接收,那么就会发生死锁。当发送的值为6
时,这正是发生的情况,因为然后主goroutine尝试在quit
上发送一个值,但是如果工作goroutine在default
分支中尝试在res
上发送,那么两个goroutine都尝试发送一个值,而没有一个尝试接收!这两个通道都是无缓冲的,因此这是一个死锁。
在工作goroutine中,你必须使用适当的case
分支将值发送到res
,而不是使用default
分支:
select {
case <-quit:
fmt.Println("检测到退出信号!")
return
case res <- idx:
fmt.Println("goroutine正在执行任务..")
idx++
}
在主goroutine中,你必须从for
循环中跳出,以便主goroutine可以结束,从而程序也可以结束:
if r == 6 {
quit <- true
break
}
这次的输出结果(在Go Playground上尝试):
goroutine正在执行任务..
我收到了:0
我收到了:1
goroutine正在执行任务..
goroutine正在执行任务..
我收到了:2
我收到了:3
goroutine正在执行任务..
goroutine正在执行任务..
我收到了:4
我收到了:5
goroutine正在执行任务..
goroutine正在执行任务..
英文:
The problem is that in the goroutine you use a select
to check if it should abort, but you use the default
branch to do the work otherwise.
The default
branch is executed if no communications (listed in case
branches) can proceed. So in each iteration quit
channel is checked, but if it cannot be received from (no need to quit yet), default
branch is executed, which unconditionally tries to send a value on res
. Now if the main goroutine is not ready to receive from it, this will be a deadlock. And this is exactly what happens when the sent value is 6
, because then the main goroutine tries to send a value on quit
, but if the worker goroutine is in the default
branch trying to send on res
, then both goroutines try to send a value, and none is trying to receive! Both channels are unbuffered, so this is a deadlock.
In the worker goroutine you must send the value on res
using a proper case
branch, and not in the default
branch:
select {
case <-quit:
fmt.Println("Detected quit signal!")
return
case res <- idx:
fmt.Println("goroutine is doing stuff..")
idx++
}
And in the main goroutine you must break out from the for
loop so the main goroutine can end and so the program can end as well:
if r == 6 {
quit <- true
break
}
Output this time (try it on the Go Playground):
goroutine is doing stuff..
I received: 0
I received: 1
goroutine is doing stuff..
goroutine is doing stuff..
I received: 2
I received: 3
goroutine is doing stuff..
goroutine is doing stuff..
I received: 4
I received: 5
goroutine is doing stuff..
goroutine is doing stuff..
答案2
得分: 0
基本问题是,生产者必须在发送值之间始终检查消费者(在您的情况下是主函数)是否决定停止读取(在您的代码中,这是可选的)。问题在于,在发送quit的值之前(并且接收到之前),生产者继续在res
上发送下一个值,而消费者却无法读取 - 实际上,消费者试图在quit通道上发送值,期望生产者读取。添加了一个调试语句,可以帮助您理解:https://play.golang.org/p/mP_4VYrkZZ - 生产者试图在res上发送7并阻塞,然后消费者试图在quit上发送值并阻塞。死锁!
一种可能的解决方案如下(使用WaitGroup是可选的,只有在需要在返回之前从生产者端进行干净退出时才需要):
package main
import (
"fmt"
"sync"
)
func main() {
// WaitGroup is needed only if need a clean exit for producer
// that is the producer should have exited before consumer (main)
// exits - the code works even without the WaitGroup
var wg sync.WaitGroup
quit := make(chan bool)
res := make(chan int)
go func() {
idx := 0
for {
fmt.Println("goroutine is doing stuff..", idx)
res <- idx
idx++
if <-quit {
fmt.Println("Producer quitting..")
wg.Done()
return
}
// select {
// case <-quit:
// fmt.Println("Detected quit signal!")
// time.Sleep(1000 * time.Millisecond)
// return
// default:
// fmt.Println("goroutine is doing stuff..", idx)
// res <- idx
// idx++
// }
}
}()
wg.Add(1)
for r := range res {
if r == 6 {
fmt.Println("Consumer exit condition met:", r)
quit <- true
break
}
quit <- false
fmt.Println("I received:", r)
}
wg.Wait()
}
输出:
goroutine is doing stuff.. 0
I received: 0
goroutine is doing stuff.. 1
I received: 1
goroutine is doing stuff.. 2
I received: 2
goroutine is doing stuff.. 3
I received: 3
goroutine is doing stuff.. 4
I received: 4
goroutine is doing stuff.. 5
I received: 5
goroutine is doing stuff.. 6
Consumer exit condition met: 6
Producer quitting..
在 playground 上查看:https://play.golang.org/p/N8WSPvnqqM
英文:
The fundamental issue is that producer must always check in between sending values if the consumer (main in your case) has decided to quit reading (in your code this is optional). What's happening is even before the value of quit is sent (and received), the producer goes ahead and sends the next value on res
which the consumer never is able to read - the consumer is in fact trying to send the value on the quit channel expecting the producer to read. Added a debug statement which can help you understand : https://play.golang.org/p/mP_4VYrkZZ, - producer is trying to send 7 on res and blocking, and then consumer trying to send value on quit and blocking. Deadlock!
One possible solution is as follows (using a Waitgroup is optional, needed only if you need a clean exit from producer side before return):
package main
import (
"fmt"
"sync"
)
func main() {
//WaitGroup is needed only if need a clean exit for producer
//that is the producer should have exited before consumer (main)
//exits - the code works even without the WaitGroup
var wg sync.WaitGroup
quit := make(chan bool)
res := make(chan int)
go func() {
idx := 0
for {
fmt.Println("goroutine is doing stuff..", idx)
res <- idx
idx++
if <-quit {
fmt.Println("Producer quitting..")
wg.Done()
return
}
//select {
//case <-quit:
//fmt.Println("Detected quit signal!")
//time.Sleep(1000 * time.Millisecond)
// return
//default:
//fmt.Println("goroutine is doing stuff..", idx)
//res <- idx
//idx++
//}
}
}()
wg.Add(1)
for r := range res {
if r == 6 {
fmt.Println("Consumer exit condition met: ", r)
quit <- true
break
}
quit <- false
fmt.Println("I received: ", r)
}
wg.Wait()
}
Output:
goroutine is doing stuff.. 0
I received: 0
goroutine is doing stuff.. 1
I received: 1
goroutine is doing stuff.. 2
I received: 2
goroutine is doing stuff.. 3
I received: 3
goroutine is doing stuff.. 4
I received: 4
goroutine is doing stuff.. 5
I received: 5
goroutine is doing stuff.. 6
Consumer exit condition met: 6
Producer quitting..
On playground : https://play.golang.org/p/N8WSPvnqqM
答案3
得分: 0
由于您的要求,我将为您翻译以下内容:
由于@icza的答案非常简洁,而@Ravi的答案则采用了同步方式。
但是,因为我不想花太多精力重构代码,也不想采用同步方式,所以最终选择了defer panic recover
流程控制,如下所示:
func test(ch chan<- int, data []byte) {
defer func() {
recover()
}()
defer close(ch)
// 正常执行逻辑...
// 正常发送结果 `ch <- res`
}
// 然后在调用者的goroutine中
ch := make(chan int)
data := []byte{1, 2, 3}
go test(ch, data)
for res := range ch {
// 当您想终止测试的goroutine时:
// 故意关闭通道
//
// `go -race`会报告潜在的竞争条件,但这是可以接受的
//
// 然后测试的goroutine会因为尝试在关闭的通道上发送数据而引发panic,
// 然后进行恢复,然后退出,完美 :)
close(ch)
break
}
这种方法是否存在潜在风险?
英文:
As @icza's answer is pretty clean, which @Ravi's goes to the synchronised way.
But coz I don't want to spend that much effort to restructure the code, and also I don't want to go to the synchronised way, so eventually went to the defer panic recover
flow control, as below:
func test(ch chan<- int, data []byte) {
defer func() {
recover()
}()
defer close(ch)
// do your logic as normal ...
// send back your res as normal `ch <- res`
}
// Then in the caller goroutine
ch := make(chan int)
data := []byte{1, 2, 3}
go test(ch, data)
for res := range ch {
// When you want to terminate the test goroutine:
// deliberately close the channel
//
// `go -race` will report potential race condition, but it is fine
//
// then test goroutine will be panic due to try sending on the closed channel,
// then recover, then quit, perfect :)
close(ch)
break
}
any potential risk with this approach?
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论