A common mistake is replacing a condition notification with a shared channel. A channel message reaches only one receiving goroutine, so multiple listeners can observe different balance updates. In a donation-goal example, the $10 listener may receive $11 instead of the $10 notification while the $15 listener still succeeds. section 9.12
A mutex-only fix avoids unsafe shared access but creates a busy loop: each listener repeatedly checks the balance, wasting CPU while waiting. sync.Cond provides condition-associated waiting and lets the updater notify all waiting listeners when the balance changes. section 9.12 section 9.15
Notify every donation-goal listener
Store a sync.Cond with a mutex and a shared balance. Each listener locks the condition, waits while its goal is unmet, then prints the reached balance. The updater increments the balance while holding the same lock and calls Broadcast afterward, allowing both the $10 and $15 listeners to receive notifications. section 9.12
type Donation struct {
cond *sync.Cond #1
balance int
}
donation := &Donation{
cond: sync.NewCond(&sync.Mutex{}), #2
}
// Listener goroutines
f := func(goal int) {
donation.cond.L.Lock()
for donation.balance < goal {
donation.cond.Wait() #3
}
fmt.Printf("%d$ goal reached\n", donation.balance)
donation.cond.L.Unlock()
}
go f(10)
go f(15)
// Updater goroutine
for {
time.Sleep(time.Second)
donation.cond.L.Lock()
donation.balance++ #4
donation.cond.L.Unlock()
donation.cond.Broadcast() #5
}
The complete listing contrasts condition waiting with busy polling and shared-channel delivery: listeners test their own goals in a loop, while the updater broadcasts after each balance increment. section 9.12
Choose the coordination primitive according to the notification requirement:
- Use a shared channel when each update should be consumed by one receiving goroutine. section 9.12
- Use sync.Cond when one state change should notify multiple goroutines waiting for different conditions. section 9.12 section 9.15

