I don't understand why this works with an unbuffered channel, or why a wait group is needed

Issue

In this code, I call a function which counts the number of letters in a string, and return a map of runes. To leverage concurrency, I call the function using goroutines:

func ConcurrentFrequency(l []string) FreqMap {
    var wg sync.WaitGroup
    wg.Add(len(l))
    m := FreqMap{}

    // Using unbuffered channel
    // ch := make(chan FreqMap, len(l))
    ch := make(chan FreqMap)

    for _, s := range l {
        go func(s string, ch chan<- FreqMap) {
            defer wg.Done()
            ch <- Frequency(s)
        }(s, ch)
    }
    go func() {
        wg.Wait()
        close(ch)
    }()

    for cm := range ch {
        for r, n := range cm {
            m[r] += n
        }
    }

    return m
}

If I try this code without using a waitgroup and the goroutine which closes the channel:

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

, then I get a deadlock.

What I don’t understand, is why I am able to loop over the unbuffered channel, and read multiple maps from it.

This is the full program:
https://go.dev/play/p/zUwr_HvTT5w

And the concurrent method is barely faster than the sequential method:

goos: linux
goarch: amd64
pkg: letter
cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
BenchmarkSequentialFrequency
BenchmarkSequentialFrequency-2              2820            367128 ns/op           17571 B/op         13 allocs/op
BenchmarkConcurrentFrequency
BenchmarkConcurrentFrequency-2              4237            282632 ns/op           12682 B/op         72 allocs/op
PASS
ok      letter  3.320s

Solution

A for-range loop over a channel continues until the channel is closed.

If you remove the goroutine that eventually closes the channel, the for loop can never terminate. Once all goroutines sending values are done there is only one goroutine remaining and it is blocked forever, waiting for the channel to be closed.

Buffered channels have nothing to do with this problem. They only help with blocked senders, but here the problem is a blocked receiver.

Answered By – Peter

Answer Checked By – Timothy Miller (GoLangFix Admin)

Leave a Reply

Your email address will not be published.