A happens-before relation guarantees that one concurrent event occurs before another for synchronization and memory visibility. Within one goroutine, program order supplies this guarantee; across goroutines, synchronization must establish it. section 8.4.2
Important Go ordering guarantees include:
- Creating a goroutine happens before its execution begins, but a goroutine's exit does not automatically happen before later events. section 8.4.2
- A channel send happens before its corresponding receive completes, and closing a channel happens before a receive observes that closure. section 8.4.2
- An unbuffered channel can order a sender and receiver differently from a buffered channel; choosing the wrong capacity can leave a shared access racing. section 8.4.2
- Synchronization composes by transitivity, so a write before a channel send can become ordered before a read after the corresponding receive. section 8.4.2
A WaitGroup mistake
Calling wg.Add(1) inside each newly launched goroutine is too late: wg.Wait can observe a zero counter and return before the last goroutine increments it. Call Add before launching the goroutine, either once for a known total or during each parent-loop iteration; call Done inside the worker. section 9.11
i := 0
ch := make(chan struct{})
go func() {
<-ch
fmt.Println(i)
}()
i++
ch <- struct{}{}
variable increment < channel send < channel receive < variable read
Trace the unbuffered-channel chain from the increment through the send and receive to the final read. section 8.4.2
wg := sync.WaitGroup{}
var v uint64
for i := 0; i < 3; i++ {
go func() { #1
wg.Add(1) #2
atomic.AddUint64(&v, 1) #3
wg.Done() #4
}()
}
wg.Wait() #5
fmt.Println(v)
Inspect how placing Add inside the worker allows Wait to unblock before all workers have registered. section 9.11




