Find the two WaitGroup bugs.
Question 495MediumGo 1.22 to 1.25
func worker(id int, wg sync.WaitGroup) { // BUG 2: copied
defer wg.Done()
fmt.Println("worker", id)
}
func main() {
var wg sync.WaitGroup
for i := range 3 {
go func() {
wg.Add(1) // BUG 1: Add inside the goroutine
defer wg.Done()
fmt.Println(i)
}()
}
wg.Wait() // may return immediately
for i := range 3 {
wg.Add(1)
go worker(i, wg) // Done runs on a copy, so Wait blocks forever
}
wg.Wait() // fatal error: all goroutines are asleep - deadlock!
}
Bug 1: Add must happen before the go statement, in the goroutine that later calls Wait. Otherwise Wait can run while the counter is still 0, return, and main exits before anything prints. Bug 2: WaitGroup contains a noCopy guard. Passing it by value means Done decrements a copy, the original never reaches zero, and the program deadlocks. go vet flags this. Pass *sync.WaitGroup, or better, let a closure capture it.
Go 1.25 added wg.Go(func()), which calls Add and Done for you and makes both bugs impossible. For error propagation and cancellation, use errgroup.Group.
More on Tricky Output & Code-Review Puzzles
- Q493Why won't m["a"].count++ compile, and what happens with a nil map?
- Q494Spot the bug: why is err nil after the if block?
- Q496Why does this loop spin at 100% CPU after one channel closes?
- Q497What does this select print, and where does the break go?
- Q498What does each channel operation do on nil, open, and closed channels?
- Q499Which of these deadlock, and why does Go detect some deadlocks but not others?