就像在这里我创建了一个游乐场样本:sGgxEh40ev,但无法让它运作。
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)
}
输出:
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!
这可能吗?我哪里错了
答案 0 :(得分:5)
问题是在goroutine中你使用select
检查它是否应该中止,但你使用default
分支来做其他工作。
如果没有通信(在default
分支中列出)可以继续,则执行case
分支。因此,在每次迭代中都会检查quit
通道,但如果无法接收(无需退出),则执行default
分支,无条件地尝试发送res
上的值。现在如果主goroutine还没有准备好接收它,那将是一个僵局。这正是发送值为6
时发生的情况,因为主goroutine尝试在quit
上发送一个值,但是如果工作者goroutine在default
分支中试图发送res
,然后两个goroutines尝试发送一个值,没有人试图接收!两个通道都是无缓冲的,所以这是一个死锁。
在worker goroutine中,您必须使用正确的res
分支在case
上发送值,而不是default
分支:
select {
case <-quit:
fmt.Println("Detected quit signal!")
return
case res <- idx:
fmt.Println("goroutine is doing stuff..")
idx++
}
在主要的goroutine中,你必须从for
循环中突破,这样主要的goroutine才能结束,所以程序也可以结束:
if r == 6 {
quit <- true
break
}
这一次输出(在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..
答案 1 :(得分:0)
根本问题是,如果消费者(在您的情况下是主要的)决定退出阅读(在您的代码中这是可选的),生产者必须始终在发送值之间签入。甚至在发送(和接收)quit的值之前发生了什么,生产者继续前进并发送消费者永远无法读取的res
上的下一个值 - 消费者实际上正在尝试在退出通道上发送期望生产者读取的值。添加了一个可以帮助您理解的调试语句:https://play.golang.org/p/mP_4VYrkZZ, - 生产者正试图在res和阻塞上发送7,然后消费者尝试在退出和阻止时发送值。死锁!
一种可能的解决方案如下(使用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..
答案 2 :(得分:0)
由于@ icza的答案非常简洁,@ Ravi将采用同步方式。
但是因为我不想花费那么多精力来重构代码,而且我也不想去同步的方式,所以最终转到defer panic recover
流量控制,如下所示:
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
}
这种方法有任何潜在的风险吗?