为什么 goroutine 会泄漏
Why goroutine leaks
我阅读了 Twelve Go Best Practices 并在第 30 页遇到了一个有趣的例子。
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")
}
程序员提到,上面的代码恰好是这样的:
the goroutine is blocked on the chan write
the goroutine holds a reference to the chan
the chan will never be garbage collected
为什么goroutine会卡在这里?主线程被阻塞,直到它从 goroutine 接收到数据。在它继续 for 循环之后。不是吗?
为什么 errc chan 永远不会被垃圾回收?因为我没有关闭通道,goroutine 结束后?
我看到的一个问题是在 goroutines 启动后 broadcastMsg()
内部:
for _ = range addrs {
if err := <-errc; err != nil {
return err
}
}
如果从 errc
、broadcastMsg()
returns 收到非 nil
error
立即出现该错误,并且不会从通道,这意味着进一步的 goroutines 将永远不会被解锁,因为 errc
是无缓冲的。
可能的修复
一个可能的解决方法是使用一个缓冲通道,它足够大,不会阻塞任何 goroutines,在这种情况下:
errc := make(chan error, len(addrs))
或者即使从通道接收到非nil
error
,仍然继续接收与在其上发送的 goroutines 一样多的次数:
var errRec error
for _ = range addrs {
if err := <-errc; err != nil {
if errRec == nil {
errRec = err
}
}
}
return errRec
或者如 slide #33 上的链接谈话中所述:使用 "quit" 通道来防止启动的 goroutine 在 broadcastMsg()
具有 completed/returned 之后保持阻塞状态。
您有一个包含两个地址的列表(localhost,google)。您要向其中的每一个发送一条消息 (hi),每个地址使用一个 goroutine。 goroutine 将错误(可能为 nil)发送到 errc 通道。
如果你向一个通道发送一些东西,你还需要一些东西从那个通道读取值,否则它会阻塞(除非它是一个缓冲通道,但即使是缓冲通道一旦它们的缓冲区已满也会阻塞)。
所以你的阅读循环看起来像这样:
for _ = range addrs {
if err := <-errc; err != nil {
return err
}
}
如果第一个地址returns错误其中不为nil,则循环returns。后续错误值永远不会从通道中读取,因此它会阻塞。
我阅读了 Twelve Go Best Practices 并在第 30 页遇到了一个有趣的例子。
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")
}
程序员提到,上面的代码恰好是这样的:
the goroutine is blocked on the chan write
the goroutine holds a reference to the chan
the chan will never be garbage collected
为什么goroutine会卡在这里?主线程被阻塞,直到它从 goroutine 接收到数据。在它继续 for 循环之后。不是吗?
为什么 errc chan 永远不会被垃圾回收?因为我没有关闭通道,goroutine 结束后?
我看到的一个问题是在 goroutines 启动后 broadcastMsg()
内部:
for _ = range addrs {
if err := <-errc; err != nil {
return err
}
}
如果从 errc
、broadcastMsg()
returns 收到非 nil
error
立即出现该错误,并且不会从通道,这意味着进一步的 goroutines 将永远不会被解锁,因为 errc
是无缓冲的。
可能的修复
一个可能的解决方法是使用一个缓冲通道,它足够大,不会阻塞任何 goroutines,在这种情况下:
errc := make(chan error, len(addrs))
或者即使从通道接收到非nil
error
,仍然继续接收与在其上发送的 goroutines 一样多的次数:
var errRec error
for _ = range addrs {
if err := <-errc; err != nil {
if errRec == nil {
errRec = err
}
}
}
return errRec
或者如 slide #33 上的链接谈话中所述:使用 "quit" 通道来防止启动的 goroutine 在 broadcastMsg()
具有 completed/returned 之后保持阻塞状态。
您有一个包含两个地址的列表(localhost,google)。您要向其中的每一个发送一条消息 (hi),每个地址使用一个 goroutine。 goroutine 将错误(可能为 nil)发送到 errc 通道。
如果你向一个通道发送一些东西,你还需要一些东西从那个通道读取值,否则它会阻塞(除非它是一个缓冲通道,但即使是缓冲通道一旦它们的缓冲区已满也会阻塞)。
所以你的阅读循环看起来像这样:
for _ = range addrs {
if err := <-errc; err != nil {
return err
}
}
如果第一个地址returns错误其中不为nil,则循环returns。后续错误值永远不会从通道中读取,因此它会阻塞。