gogoroutine

Goroutines with sync.WaitGroup end before last wg.Done()


I have an example code (you can find it on Go Playground):

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    messages := make(chan int)
    var wg sync.WaitGroup
    var result []int

    // you can also add these one at 
    // a time if you need to 

    wg.Add(1)
    go func() {
        defer wg.Done()
        time.Sleep(time.Second * 1)
        messages <- 1
    }()
    wg.Add(1)
    go func() {
        defer wg.Done()
        time.Sleep(time.Second * 1)
        messages <- 2
    }() 
    wg.Add(1)
    go func() {
        defer wg.Done()
        time.Sleep(time.Second * 1)
        messages <- 3
    }()
    go func() {
        for i := range messages {
            fmt.Println(i)
        result = append(result, i)
        }

    }()

    wg.Wait()
    fmt.Println(result)
}

I got this output:

2
1
[2 1]

I think I know why is it happening, but I can't solve it. There is 3 item in the WaitGroup I mean the three goroutine, and the 4th groutine consume the data from the channel. When the last groutine say wg.Done() the program is over because of the wg.Wait() said every goroutine finished and the last goroutine result the 4th goroutine couldn't have consume, because the program ended. I tryed to add plus one with wg.Add(1) and the wg.Done() in the 4th function but in this case I got deadlock.


Solution

  • The last goroutine you spawn—that one intended to collect the results—is not waited by main() so wg.Wait() in there returns, main() quits and reaps the remaining goroutines. Supposedly just a single—collecting—goroutine remains by that time but it fails to update the slice.

    Also note that due to the same reason you have a data race in your program: by the time main() reads the slice of results, it does not know whether it's safe to read it—that is, whether the writer is done writing to there.

    An easy fix is to do add wg.Add(1) for that goroutine and defer wg.Done() in it, too.

    A better solution is to close() the messages channel after wg.Wait() and before reading from the slice. This would make the collecting goroutine's range loop to terminate and this would also create a proper synchronization point between that goroutine and main().