Post

Done channels

If you need multiple goroutines to wait for the same thing, use a done channel.

A done channel is defined like this.

1
var done = make(chan struct{})

Defining it as a chan struct{} indicates to readers that no data will be sent on this channel and that it is only used for signaling.

Once the thing you want finished is completed, close the channel. This will immediately unblock any goroutines waiting to read from it and ensures that future reads will not block.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import (
    "sync"
    "time"
)

type msg struct {
    content string
    done    chan struct{}
}

func (m *msg) Init() {
    time.Sleep(time.Second) // Simulate a long-running process.
    m.content = "Hello, world!"
    close(m.done)
}

func newMsg() *msg {
    return &msg{"", make(chan struct{})}
}

func main() {
    m := newMsg()
    var wg sync.WaitGroup
    go m.Init()
    for range 5 {
        wg.Add(1)
        go func() {
            defer wg.Done()
            <-m.done
            println("msg:", m.content)
        }()
    }
    wg.Wait() // Blocks; waits for the goroutines above to finish.
    <-m.done  // Does not block; m.done is already closed.
}

Playground

This is the same mechanism used by the context package to signal to all downstream functions that a context has been canceled.