英文:
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")
}
程序员提到了上述代码的情况:
- goroutine 在 chan 写入时被阻塞
- goroutine 持有 chan 的引用
- chan 永远不会被垃圾回收
为什么 goroutine 在这里被阻塞?主线程被阻塞,直到它从 goroutine 接收到数据后才继续执行 for 循环,不是吗?
为什么 errc chan 永远不会被垃圾回收?因为我没有在 goroutine 完成后关闭通道吗?
英文:
I read Twelve Go Best Practices and encounter and interesting example on page 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 programmer mentioned, that happens to the code above:
the goroutine is blocked on the chan write
the goroutine holds a reference to the chan
the chan will never be garbage collected
Why the goroutine is blocked here? The main thread is blocked, until it receive data from goroutine. After it continues the for loop. Not?
Why the errc chan will be never garbage collected? Because I do not close the channel, after goroutine is finished?
答案1
得分: 10
我看到的一个问题是,在broadcastMsg()
函数内部,在goroutine启动后:
for _ = range addrs {
if err := <-errc; err != nil {
return err
}
}
如果从errc
通道接收到非nil
的error
,broadcastMsg()
函数会立即返回该错误,并且不会从通道接收更多的值,这意味着后续的goroutine将永远不会被解除阻塞,因为errc
是无缓冲的。
可能的修复方法
一个可能的修复方法是使用一个足够大的缓冲通道,以确保不会阻塞任何一个goroutine,例如:
errc := make(chan error, len(addrs))
或者,即使从通道接收到非nil
的error
,仍然继续接收与goroutine发送次数相同的次数:
var errRec error
for _ = range addrs {
if err := <-errc; err != nil {
if errRec == nil {
errRec = err
}
}
}
return errRec
或者如链接中的演讲中所提到的第33张幻灯片:使用一个“quit”通道,在broadcastMsg()
完成/返回后防止已启动的goroutine保持阻塞状态。
英文:
One problem I see is that inside broadcastMsg()
after goroutines have started:
for _ = range addrs {
if err := <-errc; err != nil {
return err
}
}
If a non-nil
error
is received from errc
, broadcastMsg()
returns immediately with that error and does not receive futher values from the channel, which means further goroutines will never get unblocked because errc
is unbuffered.
Possible Fixes
A possible fix would be to use a buffered channel, big enough to not block any of the goroutines, in this case:
errc := make(chan error, len(addrs))
Or even if a non-nil
error
is received from the channel, still proceed to receive as many times as many goroutines send on it:
var errRec error
for _ = range addrs {
if err := <-errc; err != nil {
if errRec == nil {
errRec = err
}
}
}
return errRec
Or as mentioned in the linked talk on slide #33: use a "quit" channel to prevent the started goroutines to remain blocked after broadcastMsg()
has completed/returned.
答案2
得分: 5
你有一个包含两个地址(localhost、google)的列表。对于每个地址,你都使用一个goroutine发送一条消息(hi)。这个goroutine会将错误(可能为nil)发送到errc通道。
如果你向一个通道发送了内容,你也需要有一个读取该通道值的机制,否则它会阻塞(除非它是一个带缓冲区的通道,但即使是带缓冲区的通道,一旦缓冲区满了也会阻塞)。
所以你的读取循环看起来像这样:
for _ = range addrs {
if err := <-errc; err != nil {
return err
}
}
如果第一个地址返回的错误不为nil,循环就会返回。后续的错误值从通道中没有被读取,因此它会阻塞。
英文:
You have a list of two addresses (localhost, google). To each of these you're sending a message (hi), using one goroutine per address. And the goroutine sends error (which may be nil) to errc channel.
If you send something to a channel, you also need something that reads the values from that channel, otherwise it will block (unless it's a buffered channel, but even buffered channels block once their buffer is full).
So your reading loop looks like this:
for _ = range addrs {
if err := <-errc; err != nil {
return err
}
}
If the first address returns an error which is not nil, the loop returns. The subsequent error values are never read from the channel thus it blocks.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论