liveBook · Manning

Happens-before relation

As explained in 100 Go Mistakes and How to Avoid Them

A happens-before relationship is a formal ordering relation in which one concurrent operation is guaranteed to occur before another for synchronization and memory-visibility purposes.

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

How 100 Go Mistakes and How to Avoid Them covers it

How deep it goes
Explains program order, goroutine and channel guarantees, transitivity, and WaitGroup misuse, but does not develop memory-fence internals.
Examples
Uses shared-variable reads and writes, buffered and unbuffered channel snippets, and a WaitGroup loop with misplaced Add.
What it assumes
Assumes basic goroutines and shared variables while establishing the relevant Go memory-model and synchronization guarantees.
What you can do after
Diagnose data races and premature synchronization, then choose channel or WaitGroup ordering that establishes the required relationship.
How it connects
Connects goroutines, channels, data races, synchronization, memory visibility, memory fences, and sync.WaitGroup.

Also explained in

Compare how 5 books explain happens-before relation →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 Go Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.