为什么goroutine泄漏

zer*_*ing 5 go goroutine

我在第30页阅读了Twelve Go Best Practices和遭遇以及有趣的例子.

func sendMsg(msg, addr string) error {
    conn, err := net.Dial("tcp", addr)
    if err != nil {
        return err
    }
    defer conn.Close()
    _, err = fmt.Fprint(conn, msg)
    return err
} 

func broadcastMsg(msg string, addrs []string) error {
    errc := make(chan error)
    for _, addr := range addrs {
        go func(addr string) {
            errc <- sendMsg(msg, addr)
            fmt.Println("done")
        }(addr)
    }

    for _ = range addrs {
        if err := <-errc; err != nil {
            return err
        }
    }
    return nil
}

func main() {
    addr := []string{"localhost:8080", "http://google.com"}
    err := broadcastMsg("hi", addr)

    time.Sleep(time.Second)

    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println("everything went fine")
}
Run Code Online (Sandbox Code Playgroud)

程序员提到,这恰好发生在上面的代码中:

the goroutine is blocked on the chan write
the goroutine holds a reference to the chan
the chan will never be garbage collected
Run Code Online (Sandbox Code Playgroud)

为什么goroutine在这里受阻?主线程被阻塞,直到它从goroutine接收数据.在继续for循环之后.不?

为什么errc chan永远不会被垃圾收集?因为我没有关闭频道,完成goroutine后?

icz*_*cza 9

我看到的一个问题是broadcastMsg()在goroutines启动后的内部:

for _ = range addrs {
    if err := <-errc; err != nil {
        return err
    }
}
Run Code Online (Sandbox Code Playgroud)

如果非nil error从接收errc,broadcastMsg()与该错误立即返回,并且不从通道,这意味着进一步够程将永远不会因为畅通接受任何最新值errc是缓冲的.

可能的修复

可能的解决方法是使用缓冲通道,大到足以阻止任何goroutine,在这种情况下:

errc := make(chan error, len(addrs))
Run Code Online (Sandbox Code Playgroud)

或者即使nil error从频道收到非,仍然会继续接收尽可能多的goroutines发送的次数:

var errRec error
for _ = range addrs {
    if err := <-errc; err != nil {
        if errRec == nil {
            errRec = err
        }
    }
}
return errRec
Run Code Online (Sandbox Code Playgroud)

或者如幻灯片#33上的链接谈话中所述:使用"退出"通道来防止启动的goroutine在broadcastMsg()完成/返回后保持阻塞状态.

  • 我太蠢了.非常感谢. (2认同)

fro*_*utz 5

您有一个包含两个地址的列表(本地主机、谷歌)。您向其中的每一个发送消息 (hi),每个地址使用一个 goroutine。goroutine 将错误(可能为零)发送到 errc 通道。

如果你向一个通道发送一些东西,你还需要一些从该通道读取值的东西,否则它会阻塞(除非它是一个缓冲通道,但即使缓冲通道一旦它们的缓冲区已满就会阻塞)。

所以你的阅读循环看起来像这样:

for _ = range addrs {
    if err := <-errc; err != nil {
        return err
    }
}
Run Code Online (Sandbox Code Playgroud)

如果第一个地址返回一个非 nil 的错误,则循环返回。永远不会从通道中读取后续错误值,因此它会阻塞。