Wait for go routines to finish then read from channel

Issue

How do I wait for all go routines to finish and then read all the data from a channel?

Why is this example stuck waiting for the go routines to finish?

Go Playground

package main

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

func doStuff(i int, wg *sync.WaitGroup, messages chan<- string) {
    defer wg.Done()
    time.Sleep(time.Duration(i) * time.Second)
    messages <- fmt.Sprintf("Doing stuff...%d", i)

}

func doMoreStuff(i int, wg *sync.WaitGroup, messages chan<- string) {
    defer wg.Done()
    time.Sleep(time.Duration(i) * time.Second)
    messages <- fmt.Sprintf("Doing more stuff...%d", i)
}

func main() {
    var wg sync.WaitGroup
    var messages = make(chan string)

    for start := time.Now();; {
        elapsedTime := time.Since(start)
        fmt.Println(elapsedTime)
        if elapsedTime > time.Duration(3) * time.Second {
            fmt.Println("BREAK")
            break
        }

        for i := 0; i < 10; i++ {
            wg.Add(1)
            go doStuff(i, &wg, messages)

            wg.Add(1)
            go doMoreStuff(i, &wg, messages)
        }
        time.Sleep(time.Duration(1) * time.Second)
    }
    fmt.Println("WAITING")
    wg.Wait()
    fmt.Println("DONE")
    for message := range messages {
        fmt.Println(message)
    }
}

Solution

If you want to wait for all goroutines to finish that send messages on a channel, and you want to start reading the messages after that, then you have no choice but to use a buffered channel that can “host” all the messages that can be sent on it by the goroutines.

This isn’t something practical. And even if you’d go down this path, you would be able to receive and print the messages, but the for range loop doing so would never terminate because that only terminates when it receives all messages that were sent on the channel before it was closed, but you never close the channel. You may check this “half-working” solution on the Go Playground.

Instead launch a goroutine that waits for others to finish, and then close the channel:

go func() {
    fmt.Println("WAITING")
    wg.Wait()
    close(messages)
}()

So now you may use for range to receive messages in the main goroutine:

for message := range messages {
    fmt.Println(message)
}

fmt.Println("DONE")

Try this one on the Go Playground.

This solution is still not perfect: it first has to launch all goroutines, and only then it tries to receive values, and all the launched goroutines will be blocked on their send operation (until main is ready to receive those).

A better solution may be to launch a goroutine that receives the values, preferably before the goroutines that send messages on the channel (else they would be blocked on their sends anyway):

go func() {
    for message := range messages {
        fmt.Println(message)
    }
}()

And wait for the goroutines and close the channel at the end of main():

fmt.Println("WAITING")
wg.Wait()
close(messages)

The problem with this is that when main() ends, so does your app, it does not wait for other non-main goroutines to finish. And this means it will not wait for the “consumer” goroutine to receive the messages.

To wait for that “consumer”, you may use an additional sync.WaitGroup:

var wg2 sync.WaitGroup
wg2.Add(1)
go func() {
    defer wg2.Done()
    for message := range messages {
        fmt.Println(message)
    }
}()

// And the end of `main()`:

fmt.Println("WAITING")
wg.Wait()
close(messages)

fmt.Println("DONE")
wg2.Wait()

Try this one on the Go Playground.

Answered By – icza

Answer Checked By – Timothy Miller (GoLangFix Admin)

Leave a Reply

Your email address will not be published.