为了账号安全,请及时绑定邮箱和手机立即绑定

这段带有无缓冲通道的代码会导致 Go 中的 goroutine 泄漏吗?

这段带有无缓冲通道的代码会导致 Go 中的 goroutine 泄漏吗?

Go
侃侃尔雅 2023-06-19 17:03:47
我在用goroutines和channels写一些golang并发代码,怀疑我的代码可能会导致goroutine泄露。我的情况类似下面的代码,或者你可以打开这个go playground链接。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 返回时,三个 goroutine 被阻止发送到无缓冲通道,并且会出现 goroutine 泄漏。表明So only if the channel was unbuffered the leak would occur.Go 编程语言建议:我们可以在测试期间使用一个方便的技巧:如果我们不在取消事件中从 main 返回,而是执行对 panic 的调用,那么运行时将转储程序中每个 goroutine 的堆栈。如果主 goroutine 是唯一剩下的 goroutine,那么它已经完成了自己的清理工作。但是如果还有其他goroutines,可能是没有正确取消,或者已经取消了,但是取消需要时间;进行一些调查可能是值得的。恐慌转储通常包含足够的信息来区分这些情况。因此,我panic("Goroutine Resource Leak Test")在 main 函数的末尾添加来验证我的假设。但是,panic dump 仅包含 main goroutine,即没有资源泄漏。Adding num: 0 to chanAdding num: 1 to chanAdding num: 2 to chanpanic: Goroutine Resource Leak Testgoroutine 1 [running]:main.main()    /tmp/sandbox011109649/prog.go:21 +0xc0谁能帮忙解释一下为什么没有 goroutine 泄漏,或者如果有泄漏,我应该如何获得正确的恐慌转储任何建议将不胜感激,在此先感谢!
查看完整描述

1 回答

?
犯罪嫌疑人X

TA贡献2080条经验 获得超4个赞

您的代码的问题是双重的。

首先,理论上存在goroutine泄漏,因为任何向容量为零的通道(无缓冲通道或已满缓冲通道)发送值的尝试都会阻塞发送 goroutine,直到在该通道上完成接收操作。

所以,是的,根据通道工作方式的定义,所有三个 goroutine 都将在numChan <- num语句中被阻止。

其次,由于 Go 的一些修改,panic默认情况下未处理的只转储恐慌 goroutine 的堆栈跟踪。如果你想转储所有活动的 goroutines 的堆栈,你必须调整运行时——来自包的文档runtime

GOTRACEBACK变量控制当 Go 程序由于未恢复的恐慌或意外的运行时条件而失败时生成的输出量。默认情况下,失败会打印当前 goroutine 的堆栈跟踪,省略运行时系统内部的函数,然后以退出代码 2 退出。如果没有当前 goroutine 或失败是,则失败会打印所有 goroutine 的堆栈跟踪运行时内部。GOTRACEBACK=none完全省略 goroutine 堆栈跟踪。GOTRACEBACK=single(默认值)的行为如上所述。GOTRACEBACK=all为所有用户创建的 goroutines 添加堆栈跟踪。GOTRACEBACK=system类似于“all”,但为运行时函数添加堆栈帧并显示运行时内部创建的 goroutine。GOTRACEBACK=crash类似于“系统”,但以特定于操作系统的方式崩溃而不是退出。例如,在 Unix 系统上,崩溃SIGABRT引发核心转储。由于历史原因,GOTRACEBACK设置 0、1 和 2 分别是 none、all 和 system 的同义词。The runtime/debugpackage 的SetTraceback功能允许在运行时增加输出量,但它不能减少到低于环境变量指定的量。


另请注意,您不能使用计时器进行(模拟)同步:在玩具示例中,这可能有效,但在现实生活中,没有什么能阻止您的三个 goroutine 在您的主 goroutine 在调用中花费的时间跨度内没有机会运行到time.Sleep——所以结果可能是任何数量的派生 goroutines 已经运行:从 0 到 3。

在那里添加一个事实,即当main退出运行时时,运行时只会杀死所有未完成的活动 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

    }

}

游乐场



查看完整回答
反对 回复 2023-06-19
  • 1 回答
  • 0 关注
  • 116 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信