英文:
Do the 'done' channel and default case lead to goroutine leak
问题
有两个类似的案例我想与您进行比较 - 唯一的区别是值生成的方式。
- 第一个案例:在select的一个case中生成值
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
generateValues := func(done <-chan interface{}) <-chan int {
values := make(chan int)
go func() {
defer fmt.Println("All values generated")
defer close(values)
for {
select {
case <-done:
fmt.Println("DONE")
return
case values <- rand.Int():
fmt.Println("Generated")
}
}
}()
return values
}
done := make(chan interface{})
values := generateValues(done)
for i := 0; i < 3; i++ {
fmt.Printf("Received value: %v\n", <-values)
}
fmt.Println("Closing the channel")
close(done)
time.Sleep(5 * time.Second)
}
Go playground: https://go.dev/play/p/edlOSqdZ9ys
- 第二个案例:在default case中生成值
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
generateValues := func(done <-chan interface{}) <-chan int {
values := make(chan int)
go func() {
defer fmt.Println("All values generated")
defer close(values)
for {
select {
case <-done:
fmt.Println("DONE")
return
default:
values <- rand.Int()
fmt.Println("Generated")
}
}
}()
return values
}
done := make(chan interface{})
values := generateValues(done)
for i := 0; i < 3; i++ {
fmt.Printf("Received value: %v\n", <-values)
}
fmt.Println("Closing the channel")
close(done)
time.Sleep(5 * time.Second)
}
Go playground: https://go.dev/play/p/edlOSqdZ9ys
正如您所看到的,第二个案例似乎导致了'Done'未打印和与'defer'相关的调用未被调用的情况。我相信这里存在goroutine泄漏,但无法清楚地解释。我期望的行为与第一个案例相同。
有人可以帮助理解它们之间的区别吗?
英文:
There are two similar cases I would like to compare with you - the only difference is the way of handling values generation
- the first case: values generation in one of case of select
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
generateValues := func(done <-chan interface{}) <-chan int {
values := make(chan int)
go func() {
defer fmt.Println("All values generated")
defer close(values)
for {
select {
case <-done:
fmt.Println("DONE")
return
case values <- rand.Int():
fmt.Println("Generated")
}
}
}()
return values
}
done := make(chan interface{})
values := generateValues(done)
for i := 0; i < 3; i++ {
fmt.Printf("Received value: %v\n", <-values)
}
fmt.Println("Closing the channel")
close(done)
time.Sleep(5 * time.Second)
}
Go playground: https://go.dev/play/p/edlOSqdZ9ys
- the second case: value generation in default case
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
generateValues := func(done <-chan interface{}) <-chan int {
values := make(chan int)
go func() {
defer fmt.Println("All values generated")
defer close(values)
for {
select {
case <-done:
fmt.Println("DONE")
return
default:
values <- rand.Int()
fmt.Println("Generated")
}
}
}()
return values
}
done := make(chan interface{})
values := generateValues(done)
for i := 0; i < 3; i++ {
fmt.Printf("Received value: %v\n", <-values)
}
fmt.Println("Closing the channel")
close(done)
time.Sleep(5 * time.Second)
}
Go playground: https://go.dev/play/p/edlOSqdZ9ys
As you can see the second case seems leading to the situation that the 'Done' is not printed and calls related to 'defer' are not invoked. I believe we have here goroutine leaks, but cannot clearly explain it. I expected the same behaviour like in the first case.
Could someone please help in understanding the difference between them?
答案1
得分: 1
在第二种情况下,生成的 goroutine 不太可能接收到 done
消息。由于 default
分支总是被执行,当主 goroutine 接收到最后一个值后,生成的 goroutine 会再次执行,进入 default
分支,并阻塞等待写入 values
通道。在等待期间,主 goroutine 关闭了 done
通道并终止。
这并不意味着不存在一种情况,生成的 goroutine 不接收 done
通道。要发生这种情况,必须在发送最后一个值后,生成的 goroutine 被主 goroutine 抢占,并一直运行直到关闭 done
通道。然后,如果生成的 goroutine 被调度,它可以接收到 done
信号。然而,这种事件序列的发生概率非常低。
英文:
In the second case, the generating goroutine is not likely to receive the done
message. Since the default
case is always enabled, after the main goroutine receives the last value, the generating goroutine makes another round, falls into the default
case, and blocks waiting to write to the values
channel. While it is waiting there, the main goroutine closes the done
channel and terminates.
This doesn't mean there doesn't exist an execution path where the generating goroutine doesn't receive the done channel. For this to happen, immediately after sending the last value, the generating goroutine must be preempted by the main goroutine that runs until it closes the done
channel. Then if the generating goroutine is scheduled, it can receive the done
signal. However, this sequence of events is highly unlikely.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论