Паника при попытке избежать утечки горутина - PullRequest
0 голосов
/ 02 мая 2018

Я порождаю некоторые программы и хочу передать им канал для отправки ошибок. В родительской программе I select первая ошибка и возвращаем ее, или условие wg.Done(), которое синхронизируется с закрытием канала done.

Закрытие errc откладывается во избежание утечки горутина; но это вызывает состояние гонки.

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 голосов
/ 02 мая 2018

Я получил следующую реализацию, которая опустошает буферизованный канал 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
0 голосов
/ 02 мая 2018

Ваша ошибка достаточно ясна - канал bufc (который, я полагаю, вы называете errc) закрывается до того, как g сможет отправить ему значение, потому что оператор select получает только один раз от bufc и закрывается по отсрочке. Вместо того, чтобы откладывать закрытие bufc, вам нужно выполнить некоторую синхронизацию, возможно, используя sync.WaitGroup, чтобы убедиться, что все значения переданы перед его закрытием, например, просто переместив close(bufc) в после wg.Wait():

go func() {
        wg.Wait()
        close(ch)
        close(bufc)
}()

В вашем случае, так как bufc буферизован, вам не нужно закрывать его, потому что он не блокирует на принимающей стороне, но как только вы отправите более двух подпрограмм, вам все равно нужно будет закрыть его, чтобы правильно подать сигнал.

...