登录
首页 >  Golang >  Go问答

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

来源:stackoverflow

时间:2024-04-10 13:21:36 184浏览 收藏

Golang不知道大家是否熟悉?今天我将给大家介绍《这段带有无缓冲通道的代码会导致Go中的goroutine泄漏吗?》,这篇文章主要会讲到等等知识点,如果你在看完本篇文章后,有更好的建议或者发现哪里有问题,希望大家都能积极评论指出,谢谢!希望我们能一起加油进步!

问题内容

我正在编写一些带有goroutines和channel的golang并发代码,怀疑我的代码可能会导致goroutine泄漏。我的情况类似于下面的代码,或者你可以打开这个go演示链接。

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")
} 

在我看来,当主协程返回时,三个协程被阻塞发送到无缓冲通道,就会出现协程泄漏。 go 中带有缓冲通道的 post goroutine 泄漏也表明 所以只有当通道没有缓冲时才会发生泄漏

go 编程语言建议:

我们在测试过程中可以使用一个方便的技巧:如果我们在取消时不从 main 返回,而是执行恐慌调用,那么运行时将转储程序中每个 goroutine 的堆栈。如果主 goroutine 是唯一剩下的,那么它会自行清理干净。但如果还有其他 goroutine 存在,它们可能没有被正确取消,或者可能已经被取消了,但是取消需要时间;进行一些调查可能是值得的。恐慌转储通常包含足够的信息来区分这些情况。

因此,我在主函数末尾添加了 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 默认情况下仅转储恐慌 goroutine 的堆栈跟踪。 如果您希望转储所有活动 goroutine 的堆栈,则必须调整运行时 — 从 documentation of the package runtime 开始:

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

另请注意,您绝对不能永远使用计时器来(模拟)同步:在玩具示例中这可能有效,但在现实生活中没有什么可以阻止您的三个 goroutine 不同步有机会在主 goroutine 调用 time.sleep 的时间段内运行 — 因此结果可能是任意数量的生成的 goroutine 已运行:从 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
    }
}

Playground

今天关于《这段带有无缓冲通道的代码会导致Go中的goroutine泄漏吗?》的内容就介绍到这里了,是不是学起来一目了然!想要了解更多关于的内容请关注golang学习网公众号!

声明:本文转载于:stackoverflow 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>