在试图避免goroutine泄漏时发生恐慌

时间:2018-05-02 03:33:55

标签: go memory-leaks goroutine

我正在产生一些goroutines,并希望给他们一个通道来发回错误。在父级goroutine中,我select第一个错误并返回该错误,或​​wg.Done()条件,它与关闭done频道同步。

errc的结束被推迟以避免goroutine泄漏;但它会引起竞争。

package main

import (
    "log"
    "sync"
    "time"
)

func f(ch chan<- bool, wg *sync.WaitGroup) {
    defer wg.Done()

    time.Sleep(1 * time.Second)
    log.Println("f sending a value")
    ch <- true
    log.Println("f sent a value")
}

func g(ch chan<- bool, wg *sync.WaitGroup) {
    defer wg.Done()

    time.Sleep(2 * time.Second)
    log.Println("g sending a value")
    ch <- true
    log.Println("g sent a value")
}

func main() {
    var wg sync.WaitGroup

    ch := make(chan bool)
    bufc := make(chan bool, 2)

    defer func() {
        log.Println("Closing bufc")
        close(bufc)
        log.Println("Closed bufc")
        time.Sleep(5 * time.Second)
    }()

    wg.Add(2)
    go f(bufc, &wg)
    go g(bufc, &wg)
    go func() {
        wg.Wait()
        close(ch)
    }()

    select {
    case done, ok := <-bufc:
        log.Printf("bufc closed: %v %v", done, ok)
    case <-ch:
        log.Println("ch was closed")
    }
}

结果:

❗ ~/c/scrap
(i) go run test.go
2018/05/01 20:28:03 f sending a value
2018/05/01 20:28:03 f sent a value
2018/05/01 20:28:03 bufc closed: true true
2018/05/01 20:28:03 Closing bufc
2018/05/01 20:28:03 Closed bufc
2018/05/01 20:28:04 g sending a value
panic: send on closed channel

goroutine 19 [running]:
main.g(0xc42009c000, 0xc42008a010)
        /Users/yangmillstheory/code/scrap/test.go:23 +0xb2
created by main.main
        /Users/yangmillstheory/code/scrap/test.go:42 +0x11e
exit status 2

有没有办法在不引起恐慌的情况下正确清理errc频道?我甚至需要关闭errc吗?鉴于它是缓冲的,该频道上的发送者不会阻止,所以我猜答案是否定的?

2 个答案:

答案 0 :(得分:2)

您的错误已经足够清楚了 - 因为{{1} bufc可以向errc发送值,所以g(我假设您称之为select)的频道已关闭}}语句只从bufc收到一次,并且由延迟关闭。不必推迟结束bufc,您必须进行一些同步,可能使用sync.WaitGroup确保在关闭之前发送所有值,例如只需移动{{1} } {到close(bufc)之后:

wg.Wait()

在您的情况下,go func() { wg.Wait() close(ch) close(bufc) }() 已缓存,您不必关闭它,因为它在接收端没有阻止,但是一旦您有两个以上的goroutines发送给您&#39;我仍然需要关闭它才能正确发出信号。

答案 1 :(得分:0)

我最终得到了以下实现,它消耗了缓冲的通道bufc并在所有情况下都能正常工作。

var (
    err error
    wg  sync.WaitGroup
)

ch := make(chan bool)
bufc := make(chan error, 2)

wg.Add(2)
go f(bufc, &wg)
go g(bufc, &wg)
go func() {
    wg.Wait()
    close(ch)
    close(bufc)
}()

<-ch
log.Println("Goroutines are done")
for err = range bufc {
    log.Printf("Got an error: %v", err)
}
log.Println("Returning.")
return err