进行常规的生产者 - 消费模式恐慌

时间:2017-11-10 08:50:51

标签: go goroutine

我已经实现了goroutine的生产者 - 消费者模式,如answer中所述。但它有时会因为错误而恐慌:"恐慌:同步:负WaitGroup计数器"。我有以下示例代码:

package main

import (
    "bytes"
    "encoding/gob"
    "log"
    _ "net/http/pprof"
    "sync"
)

// Test ...
type Test struct {
    PropA []int
    PropB []int
}

// Clone deep-copies a to b
func Clone(a, b interface{}) {

    buff := new(bytes.Buffer)
    enc := gob.NewEncoder(buff)
    dec := gob.NewDecoder(buff)
    enc.Encode(a)
    dec.Decode(b)
}

func main() {
    test := Test{
        PropA: []int{211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222},
        PropB: []int{111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124},
    }
    var wg, wg2 sync.WaitGroup
    ch := make(chan int, 5)
    results := make(chan Test, 5)

    // start consumers
    for i := 0; i < 4; i++ {
        wg.Add(1)
        go func(ch <-chan int, results chan<- Test) {
            defer wg.Done()
            for propA := range ch {
                var temp Test
                Clone(&test, &temp)
                temp.PropA = []int{propA}
                results <- temp
            }
        }(ch, results)
    }

    // start producing
    go func(ch chan<- int) {
        defer wg.Done()
        for _, propA := range test.PropA {
            ch <- propA
        }
        close(ch)
    }(ch)

    wg2.Add(1)
    go func(results <-chan Test) {
        defer wg2.Done()
        for tt := range results {
            log.Printf("finished propA %+v\n", tt.PropA[0])
        }
    }(results)

    wg.Wait() // Wait all consumers to finish processing jobs

    // All jobs are processed, no more values will be sent on results:
    close(results)

    wg2.Wait()
}

当我在代码上运行4-5次时,它至少会发生一次恐慌。在某些时候,错误消息是“恐慌:在封闭频道上发送&#34;”。在生产者完成发送之前,我不明白通道是如何关闭的,以及为什么Waitgroup计数器达到负数。有人可以解释一下吗?

修改 panic的stacktrace如下:(上面代码的文件名为mycode.go

panic: send on closed channel
    panic: sync: negative WaitGroup counter

goroutine 21 [running]:
sync.(*WaitGroup).Add(0xc420134020, 0xffffffffffffffff)
    /usr/local/go/src/sync/waitgroup.go:75 +0x134
sync.(*WaitGroup).Done(0xc420134020)
    /usr/local/go/src/sync/waitgroup.go:100 +0x34
panic(0x7622e0, 0x80ffa0)
    /usr/local/go/src/runtime/panic.go:491 +0x283
main.main.func1(0xc420134020, 0xc420136090, 0xc420148000, 0xc42014a000)
    /home/mycode.go:45 +0x80
created by main.main
    /home/mycode.go:39 +0x21d
exit status 2

2 个答案:

答案 0 :(得分:4)

wg上的记账由一个人关闭,因为您的制作人致电wg.Done(),但没有Add()被要求对此进行说明。恐慌与go调度程序的可变性有关,但是一旦你看到修复程序,我相信你会看到如何获得“负WaitGroup计数器”和/或“发送封闭通道”,这取决于时间。

修复很简单,只需在启动制作人之前添加wg.Add()即可。

...

wg.Add(1)
// start producing
go func(ch chan<- int) {
    defer wg.Done()
    for _, propA := range test.PropA {
        ch <- propA
    }
    close(ch)
}(ch)

...

将来,当您看到“负面WaitGroup计数器”时,可以保证您不会将AddDone的数量1:1匹配。

答案 1 :(得分:0)

所以看起来你有两点wg.Done()可能就是问题所在。 我重构了你的代码只放了一个wg.Done()点,然后我从你放的循环中删除了wg.Add(1)。 代码here