英文:
Stop a goroutine that writes to a channel indefinitely
问题
我有一个函数,它创建了一个无限循环地向通道中填充数据的goroutine,例如:
func Foo() <-chan int {
ch := make(chan int)
go func() {
defer close(ch)
for {
ch <- 1
}
}()
return ch
}
假设我们有一个消费者希望在一段时间后停止:
ch := Foo()
<-ch
<-ch
// 完成
现在我想要清理goroutine资源,包括通道。我尝试添加一个"done"通道来实现,但是我遇到了死锁问题:
func Foo() (<-chan int, chan<- bool) {
ch := make(chan int)
done := make(chan bool)
go func() {
defer close(ch)
for {
select {
case <-done:
return
default:
ch <- 1
}
}
}()
return ch, done
}
func main() {
ch, done := Foo()
<-ch
<-ch
done <- true
// 这里
}
现在,它似乎可以工作,但这只是因为程序退出了。如果我将// 这里
替换为一些IO操作(例如:http.Get("http://google.com")
),我会遇到死锁问题(fatal error: all goroutines are asleep - deadlock!
)。
我想知道是否有另一种方法来清理由Foo
函数创建的goroutine和通道。
英文:
I have a function that creates a goroutine that populates a channel indefinitely, e.g.,:
func Foo() <-chan int {
ch := make(chan int)
go func() {
defer close(ch)
for {
ch <- 1
}
}()
return ch
}
And let's say we have a consumer that would like to stop after a while:
ch:=Foo()
<-ch
<-ch
// done
Now I want to clean the goroutine resource including the channel. I tried to add a "done" channel for that but then I face a deadlock:
func Foo() (<-chan int, chan<- bool) {
ch := make(chan int)
done := make(chan bool)
go func() {
defer close(ch)
for {
select {
case <-done:
return
default:
ch <- 1
}
}
}()
return ch, done
}
func main() {
ch, done := Foo()
<-ch
<-ch
done <- true
// HERE
}
Now, it seems to work, but it is only because the program exits, if I replace // HERE
with some io operation (e.g.,: http.Get("http://google.com")
), I face a deadlock (fatal error: all goroutines are asleep - deadlock!
).
I wonder if there is another way to clean up the spawned goroutine and channel created by the Foo
function.
答案1
得分: 3
只需在启动的goroutine中将default
替换为case
:
func Foo() (<-chan int, chan<- bool) {
ch := make(chan int)
done := make(chan bool)
go func() {
defer close(ch)
for {
select {
case <-done:
return
case ch <- 1:
}
}
}()
return ch, done
}
原始代码中的default
case会发生死锁,原因如下:
- 启动的goroutine会立即进入
default
case,因为没有其他并发运行的goroutine写入done
通道,而这些goroutine会向ch
通道写入数据。然后,goroutine会在ch <- 1
行阻塞,直到其他goroutine从ch
读取值。 - 主goroutine从
ch
读取两次。这导致启动的goroutine执行两次成功的循环。然后它尝试向done
写入数据。此时,启动的goroutine可能已经检查了select
语句,进入default
case,并在ch <- 1
行阻塞。因此,主goroutine也会在done <- true
行无限期地阻塞。这导致死锁。
英文:
Just replace default
with case
inside the started goroutine:
func Foo() (<-chan int, chan<- bool) {
ch := make(chan int)
done := make(chan bool)
go func() {
defer close(ch)
for {
select {
case <-done:
return
case ch <- 1:
}
}
}()
return ch, done
}
The original code with the default
case deadlocks because of the following reasons:
- The started goroutines, which writes to
ch
channel, instantly goes to thedefault
case when there are no other concurrently running goroutines, which write todone
channel. Then the goroutines blocks at thech <- 1
line until other goroutines reads the value fromch
. - The main goroutine reads twice from
ch
. This results in two successful execution loops at the started goroutine. Then it tries writing todone
. At this time the started goroutine may already check theselect
statement, fall into thedefault
case and block atch <- 1
line. So the main goroutine also blocks indefinitely at thedone <- true
line. This results in deadlock.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论