0% completed
Waiting for Goroutines with Done Channels
On This Page
A Done Channel
Waiting for Several Goroutines
Collecting Results
Receiving the Wrong Number of Times
Choosing the Channel
Key Takeaways
One possible output, and the usual one:
The program compiles, passes go vet, and almost always prints nothing. The goroutine starts its 100 ms call, and main reaches its closing brace a moment later. As Goroutines and What They Cost explained, the program exits as soon as main returns. The goroutine is normally stopped before it can print, but nothing in the language promises either result.
Nothing in the code says that main should wait. A Go program has to state that rule itself, and the simplest way to state it uses a channel. The rest of this page shows how a channel lets main wait for one goroutine, for many goroutines, and for their results.
A Done Channel
A channel that a goroutine uses only to report that it has finished is called a done channel. The value sent through it carries no information; the act of sending is the message. The waiting goroutine receives from the channel, and the receive cannot complete until the finished goroutine has sent.
Output:
page from https://example.com/a main: the goroutine has finished
The order of the two lines is fixed. The goroutine prints before it sends, and main prints after it receives. The receive on an unbuffered channel cannot complete before the matching send. Everything the goroutine did before the send is therefore finished, and visible to main, by the time <-done completes.
The send should be the last statement of the goroutine. If it came before the call to fmt.Println, then main could continue, return, and end the program before the line was printed.
Waiting for Several Goroutines
One channel can serve any number of goroutines. Each goroutine sends once when it finishes, and the waiting goroutine receives once per goroutine it started. The program below starts one goroutine per URL and waits for all three.
Output:
3 fetches finished after 100ms
The receive loop runs exactly as many times as the start loop, because both range over urls. The receives do not say which goroutine finished, and they do not need to: after three receives, all three goroutines have sent. The three calls overlap, so the whole job takes about 100 ms instead of 300 ms.
The standard library's sync package has a dedicated type for this kind of counting. A channel and a loop of receives, however, are enough for the programs in this chapter.
Collecting Results
A done channel only reports that work has finished. When each goroutine produces a value, the same channel can carry the value instead, so that the signal and the result are sent together. Goroutines finish in any order, so each result should also carry enough information to put it in its place. Here, each goroutine sends its index together with the page.
Output:
page from https://example.com/a page from https://example.com/b page from https://example.com/c
Only main writes to the pages slice, so no two goroutines ever touch it at the same time. Each goroutine touches only its own call to fetch, and it sends the result through the channel. This division of work, where values are passed between goroutines instead of being shared, is the usual way to collect results in Go.
Receiving the Wrong Number of Times
The count of receives must match the count of sends exactly. One receive too many waits for a send that will never happen. If the receive loop in the program that waits for three goroutines ran four times, the fourth receive would wait forever:
for range 4 { // three goroutines, four receives <-done }
With every goroutine waiting, the runtime stops the program:
fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive]: main.main()
One receive too few is quieter and often worse. The waiting goroutine continues while at least one other goroutine is still trying to send on an unbuffered channel that nobody will read again. That goroutine is blocked forever and keeps its memory, which is a goroutine leak. The program below takes only the first result. It then counts how many of the goroutines it started still exist, first with an unbuffered channel and then with a buffer of three.
Output:
capacity 0: 2 goroutines left behind capacity 3: 0 goroutines left behind
With an unbuffered channel, two goroutines finish their calls and then wait forever to send, so they are left behind. A channel with one slot per goroutine solves this, because every send then completes whether or not anyone receives. Those two goroutines end normally, and the garbage collector frees the channel and its unread values once nothing refers to them.
Choosing the Channel
The programs on this page use the same tool in four slightly different ways. The table below sets them side by side.
| Goal | Channel | Receives |
|---|---|---|
| Wait for one goroutine | chan bool, unbuffered | One |
| Wait for N goroutines | One shared chan bool | Exactly N |
| Collect N results | One shared channel of results | Exactly N |
| Take some results and stop | Results channel with capacity N | As many as needed |
An unbuffered channel is the right default when the waiting goroutine will always receive every value. It keeps each sender waiting until its value has been taken, so nothing is left in a buffer. When the waiting goroutine may stop early, give the channel one slot per sender. The extra memory is small, and every sender can then finish whether or not its value is read.
Key Takeaways
- A program exits when
mainreturns, somainmust wait for the goroutines whose work it needs. - A done channel lets a goroutine report that it has finished; the send should be its last statement.
- To wait for N goroutines, start N and receive exactly N times.
- Send results through the channel, with an index or key, so that only one goroutine assembles them.
- Too many receives cause a deadlock, and too few leave senders blocked forever on an unbuffered channel.
Waiting for a goroutine means receiving a value that it sends as its final act. Counting those values is enough for a Go program to finish its goroutines' work before it exits. When not every value will be read, the channel also needs one slot per sender.
Reading Progress
0%
On This Page
A Done Channel
Waiting for Several Goroutines
Collecting Results
Receiving the Wrong Number of Times
Choosing the Channel
Key Takeaways