The Go memory model defines when a read in one goroutine is guaranteed to occur after a write in another. Its happens-before rules help prevent data races and make output deterministic. section 8.4.2
A common mistake is assuming that starting a goroutine also waits for it to finish. Goroutine creation happens before execution begins, but goroutine exit does not automatically happen before later events. Reading shared state after launching a writer can therefore race. section 8.4.2
Useful synchronization guarantees include:
- A channel send happens before the corresponding receive completes, so writes before the send can be observed safely after the receive. section 8.4.2
- Closing a channel happens before a receive that observes the closure, providing a similar ordering relationship. section 8.4.2
Buffered versus unbuffered synchronization
With an unbuffered channel, the send waits for a receive, ordering the write before the later read. With a buffered channel of capacity one, the send may complete without the receiving goroutine running, so the shared access can race. Choose synchronization according to the ordering guarantee required, not merely the presence of a channel.
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 happens-before chain from the increment through the channel send and receive to the shared read. section 8.4.2
