Search code examples
gochannelgoroutine

Channels and Wait Groups Entering Deadlock


I'm having trouble wrangling go routines and getting them to communicate back to a channel on the main go routine. To simplify, my code looks something like this:


func main() {
    channel := make(chan string)
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go performTest(channel, &wg, i)
    }

    wg.Wait()
    close(channel)

    for line := range channel {
        fmt.Print(line)
    }
}

func performTest(channel chan string, wg *sync.WaitGroup, i int) {
     defer wg.Done()
     // perform some work here
     result := fmt.sprintf("Pretend result %d", i)
     channel <- result
}

This seems to enter into some kind of a deadlock, but I don't understand why. It gets stuck on wg.Wait(), even though I would expect it to continue once all the goroutines have called Done on the wait group. What am I missing here? I'd like to wait for the goroutines, and then iterate over all results in the channel.


Solution

  • You can wait for the group and close the channel in a separate go routine. If the channel is closed, your range over the channel will end after the last sent value has been received.

    If you just wait, nothing will receive from the channel. Since the channel is unbuffered, the performTest goroutines won't be able to send. For an unbuffered channel, the send operation will block until it has been received. Therefore, the deferred wg.Done call would never happen, and your program is deadlocked. Since Done is only called after the forever-blocking send has been performed.

    func main() {
        channel := make(chan string)
        var wg sync.WaitGroup
        for i := 0; i < 10; i++ {
            wg.Add(1)
            go performTest(channel, &wg, i)
        }
        
        // this is the trick
        go func() {
            wg.Wait()
            close(channel)
        }()
    
        for line := range channel {
            fmt.Print(line)
        }
    }
    
    func performTest(channel chan string, wg *sync.WaitGroup, i int) {
        defer wg.Done()
        // perform some work here
        result := fmt.Sprintf("Pretend result %d\n", i)
        channel <- result
    }
    

    https://play.golang.com/p/5pACJzwL4Hi