Jonathan Kittell
Jonathan Kittell

Reputation: 7503

Wait for go routines to finish then read from channel

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)
    }
}

Upvotes: 3

Views: 8803

Answers (2)

AJR
AJR

Reputation: 1681

The example is stuck because you are using an unbuffered channel. Both go-routines are blocked waiting to write to the channel since nothing is ready to read from it.

You can use a buffered channel but then you would just be using it as a data storage. Channels are more useful for communication. The question is why do you want to wait until all the writers are finished? In the general case of an unknown (unlimited) number of writers you would no know how big to make your channel.

Upvotes: 0

icza
icza

Reputation: 418465

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.

Upvotes: 4

Related Questions