这段带有无缓冲通道的代码会导致 Go 中的 goroutine 泄漏吗?
Will this code with unbuffered channel cause goroutine leak in Go?
我正在用goroutines和channels写一些golang并发代码,怀疑我的代码可能会导致goroutine泄露。我的情况类似下面的代码,也可以打开这个go playground link.
func main() {
numCount := 3
numChan := make(chan int)
for i := 0; i < numCount; i++ {
go func(num int) {
fmt.Printf("Adding num: %d to chan\n", num)
numChan <- num
fmt.Printf("Adding num: %d to chan Done\n", num)
}(i)
}
time.Sleep(time.Second)
panic("Goroutine Resource Leak Test")
}
在我看来,当主 goroutine returns 时,三个 goroutine 被阻止发送到无缓冲通道,并且会出现 goroutine 泄漏。这 post goroutine leak with buffered channel in Go 也表明 So only if the channel was unbuffered the leak would occur
。
Go 编程语言 建议:
There’s a handy trick we can use during testing: if instead of returning from main in the event of cancellation, we execute a call to panic, then the runtime will dump the stack of every goroutine in the program. If the main goroutine is the only one left, then it has cleaned up after itself. But if other goroutines remain, they may not have been properly canceled, or perhaps they have been canceled but the cancellation takes time; a little investigation may be worthwhile. The panic dump often contains sufficient information to distinguish these cases.
因此,我在main函数的末尾添加了panic("Goroutine Resource Leak Test")
来验证我的假设。但是panic dump只包含了main goroutine,也就是说没有资源泄露。
Adding num: 0 to chan
Adding num: 1 to chan
Adding num: 2 to chan
panic: Goroutine Resource Leak Test
goroutine 1 [running]:
main.main()
/tmp/sandbox011109649/prog.go:21 +0xc0
谁能帮忙解释一下
- 为什么没有 goroutine 泄漏,或者
- 如果有泄漏,我应该如何获得正确的恐慌转储
任何建议将不胜感激,提前致谢!
您的代码存在双重问题。
首先,是, 理论上,goroutine 泄漏,因为任何尝试将值发送到容量为零的通道(无缓冲通道或已填充的缓冲通道)阻塞发送 goroutine,直到在该通道上完成接收操作。
所以,是的,根据通道工作方式的定义,所有三个 goroutine 都将在 numChan <- num
语句中被阻止。
其次,由于 Go 的某些修订版,未处理的 panic
默认情况下仅转储 panicing goroutine 的堆栈跟踪。
如果你想转储所有活动 goroutines 的堆栈,你必须调整 运行time — 来自 documentation of the package runtime
:
The GOTRACEBACK
variable controls the amount of output generated when a Go program fails due to an unrecovered panic or an unexpected runtime condition. By default, a failure prints a stack trace for the current goroutine, eliding functions internal to the run-time system, and then exits with exit code 2. The failure prints stack traces for all goroutines if there is no current goroutine or the failure is internal to the run-time. GOTRACEBACK=none
omits the goroutine stack traces entirely. GOTRACEBACK=single
(the default) behaves as described above. GOTRACEBACK=all
adds stack traces for all user-created goroutines. GOTRACEBACK=system
is like “all” but adds stack frames for run-time functions and shows goroutines created internally by the run-time. GOTRACEBACK=crash
is like “system” but crashes in an operating system-specific manner instead of exiting. For example, on Unix systems, the crash raises SIGABRT
to trigger a core dump. For historical reasons, the GOTRACEBACK
settings 0, 1, and 2 are synonyms for none, all, and system, respectively. The runtime/debug
package's SetTraceback
function allows increasing the amount of output at run time, but it cannot reduce the amount below that specified by the environment variable. See https://golang.org/pkg/runtime/debug/#SetTraceback.
另请注意,您不得曾经使用计时器进行(模拟)同步:在玩具示例中这可能有效,但在实际中life 没有什么能阻止你的三个 goroutines 在你的主 goroutine 调用 time.Sleep
的时间跨度内没有机会成为 运行 —— 所以结果可能是任何数量的衍生 goroutines 有 运行:从 0 到 3。
添加一个事实,当 main
退出时 运行time 只会杀死所有未完成的活动 goroutines,测试结果充其量可能是令人惊讶的。
因此,一个合适的解决方案是
- 只在需要的地方打印堆栈,
- 确保通过匹配的接收同步发送:
package main
import (
"fmt"
"log"
"runtime"
)
func dumpStacks() {
buf := make([]byte, 32 * 1024)
n := runtime.Stack(buf, true)
log.Println(string(buf[:n]))
}
func main() {
numCount := 3
numChan := make(chan int, numCount)
for i := 0; i < numCount; i++ {
go func(num int) {
fmt.Printf("Adding num: %d to chan\n", num)
numChan <- num
fmt.Printf("Adding num: %d to chan Done\n", num)
}(i)
}
dumpStacks()
for i := 0; i < numCount; i++ {
<-numChan
}
}
我正在用goroutines和channels写一些golang并发代码,怀疑我的代码可能会导致goroutine泄露。我的情况类似下面的代码,也可以打开这个go playground link.
func main() {
numCount := 3
numChan := make(chan int)
for i := 0; i < numCount; i++ {
go func(num int) {
fmt.Printf("Adding num: %d to chan\n", num)
numChan <- num
fmt.Printf("Adding num: %d to chan Done\n", num)
}(i)
}
time.Sleep(time.Second)
panic("Goroutine Resource Leak Test")
}
在我看来,当主 goroutine returns 时,三个 goroutine 被阻止发送到无缓冲通道,并且会出现 goroutine 泄漏。这 post goroutine leak with buffered channel in Go 也表明 So only if the channel was unbuffered the leak would occur
。
Go 编程语言 建议:
There’s a handy trick we can use during testing: if instead of returning from main in the event of cancellation, we execute a call to panic, then the runtime will dump the stack of every goroutine in the program. If the main goroutine is the only one left, then it has cleaned up after itself. But if other goroutines remain, they may not have been properly canceled, or perhaps they have been canceled but the cancellation takes time; a little investigation may be worthwhile. The panic dump often contains sufficient information to distinguish these cases.
因此,我在main函数的末尾添加了panic("Goroutine Resource Leak Test")
来验证我的假设。但是panic dump只包含了main goroutine,也就是说没有资源泄露。
Adding num: 0 to chan
Adding num: 1 to chan
Adding num: 2 to chan
panic: Goroutine Resource Leak Test
goroutine 1 [running]:
main.main()
/tmp/sandbox011109649/prog.go:21 +0xc0
谁能帮忙解释一下
- 为什么没有 goroutine 泄漏,或者
- 如果有泄漏,我应该如何获得正确的恐慌转储
任何建议将不胜感激,提前致谢!
您的代码存在双重问题。
首先,是, 理论上,goroutine 泄漏,因为任何尝试将值发送到容量为零的通道(无缓冲通道或已填充的缓冲通道)阻塞发送 goroutine,直到在该通道上完成接收操作。
所以,是的,根据通道工作方式的定义,所有三个 goroutine 都将在 numChan <- num
语句中被阻止。
其次,由于 Go 的某些修订版,未处理的 panic
默认情况下仅转储 panicing goroutine 的堆栈跟踪。
如果你想转储所有活动 goroutines 的堆栈,你必须调整 运行time — 来自 documentation of the package runtime
:
The
GOTRACEBACK
variable controls the amount of output generated when a Go program fails due to an unrecovered panic or an unexpected runtime condition. By default, a failure prints a stack trace for the current goroutine, eliding functions internal to the run-time system, and then exits with exit code 2. The failure prints stack traces for all goroutines if there is no current goroutine or the failure is internal to the run-time.GOTRACEBACK=none
omits the goroutine stack traces entirely.GOTRACEBACK=single
(the default) behaves as described above.GOTRACEBACK=all
adds stack traces for all user-created goroutines.GOTRACEBACK=system
is like “all” but adds stack frames for run-time functions and shows goroutines created internally by the run-time.GOTRACEBACK=crash
is like “system” but crashes in an operating system-specific manner instead of exiting. For example, on Unix systems, the crash raisesSIGABRT
to trigger a core dump. For historical reasons, theGOTRACEBACK
settings 0, 1, and 2 are synonyms for none, all, and system, respectively.The runtime/debug
package'sSetTraceback
function allows increasing the amount of output at run time, but it cannot reduce the amount below that specified by the environment variable. See https://golang.org/pkg/runtime/debug/#SetTraceback.
另请注意,您不得曾经使用计时器进行(模拟)同步:在玩具示例中这可能有效,但在实际中life 没有什么能阻止你的三个 goroutines 在你的主 goroutine 调用 time.Sleep
的时间跨度内没有机会成为 运行 —— 所以结果可能是任何数量的衍生 goroutines 有 运行:从 0 到 3。
添加一个事实,当 main
退出时 运行time 只会杀死所有未完成的活动 goroutines,测试结果充其量可能是令人惊讶的。
因此,一个合适的解决方案是
- 只在需要的地方打印堆栈,
- 确保通过匹配的接收同步发送:
package main
import (
"fmt"
"log"
"runtime"
)
func dumpStacks() {
buf := make([]byte, 32 * 1024)
n := runtime.Stack(buf, true)
log.Println(string(buf[:n]))
}
func main() {
numCount := 3
numChan := make(chan int, numCount)
for i := 0; i < numCount; i++ {
go func(num int) {
fmt.Printf("Adding num: %d to chan\n", num)
numChan <- num
fmt.Printf("Adding num: %d to chan Done\n", num)
}(i)
}
dumpStacks()
for i := 0; i < numCount; i++ {
<-numChan
}
}