WaitGroup
Ready your lightning bolts. Leave not the tower sanctity until enemies are vanquished.
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var (
imps = summon(4)
tally int
wg sync.WaitGroup
)
for _, imp := range imps {
wg.Add(1)
tally++
go vanquish(&wg, imp, 2000)
}
wg.Wait() // remain atop spire.
fmt.Printf("spire stands, %d imps vanquished\n", tally)
}
func summon(count int) []string {
imps := make([]string, count)
for imp := range imps {
imps[imp] = fmt.Sprintf("imp-%d", imp+1)
}
return imps
}
// sleep, smite, report.
func vanquish(wg *sync.WaitGroup, name string, delay int) {
defer wg.Done()
time.Sleep(time.Duration(delay) * time.Millisecond)
fmt.Println("vanquished", name)
} What you are witnessing
A sync.WaitGroup is a counter with two ceremonies: Add(n)
raises a tally for goroutines about to begin and Done() strikes one
off when each is finished. Wait() blocks until the tally has fallen back
to zero. It is a simple and sturdy way to not budge until all the work is done.
Why a wizard cares
When main returns, the program simply exits and every goroutine it spawned
is discarded mid incantation. If your wizard (main) wanders off the spire
while work (goroutines) is still in flight then the work vanishes with him.
Try it! With waitgroup on, the wizard stays at his post and every
imp is dispatched in turn. Only once the count is at zero does he fade from the
parapet and rematerialise at the door. Turn the toggle off and watch the same opening
play out differently. With no wg.Wait() to hold him in place, the wizard
fades from the parapet at once and reappears at the door. Only afterwards when already at
ground level and out of the fight, do a pair of lightning strikes drop from the empty sky
and smite two of the imps in the field. Those late bolts are the goroutines racing on past
main. They may complete but main has long since completed. A
third unstruck imp marches all the way across the ground and reaches the wizard, where a
puff of purple smoke marks the moment main is overrun.
Shape of the spell
The canonical rhythm is Add before you go, Done as you return, Wait when you must:
Add. Callwg.Add(1)before thegostatement, not inside the goroutine. If you call it inside,Waitmay run beforeAddand you'll find the tally already at zero.Donewithdefer.defer wg.Done()at the top of the goroutine survives panics, early returns and your own forgetfulness.- Pass by pointer. A
WaitGroupmust not be copied after first use. Hand goroutines a*sync.WaitGroup. Never as a value.
Pitfalls
- Negative counter will panic. Calling
Done()more times thanAdd()drives the counter below zero and the runtime ends the show with a panic. Match them exactly. - Does not collect results or errors. A
WaitGrouponly waits. For "wait and propagate the first error", reach forgolang.org/x/sync/errgroup. - No cancellation. Slow apprentices keep working until they finish. Pair with a
context.Contextwhen you need a leash. - Don't reuse without care. A
WaitGroupmay be reused onceWaitreturns but only if you're certain no apprentice from the previous round is still about to callDone. When in doubt, allocate a fresh one. Wait()is not a deadline. If even one apprentice never callsDone, the wizard waits forever.
Reagents required: sync, already in your standard pouch.