The Go Memory Model
Go · Advanced · Run with go run
Explanation
The Go Memory Model specifies when a write by one goroutine is guaranteed to be visible to a read in another. The core rule: if you do not synchronize, you have no guarantee. Synchronization is established by channels (a send happens-before the corresponding receive completes), by mutexes (an `Unlock` happens-before a later `Lock`), by `sync.Once`, and by `sync/atomic`. The model exists so the compiler and CPU are free to reorder unsynchronized operations for speed.
Real-World Scenario
A goroutine sets a `ready` boolean and another goroutine spins waiting for it. Without synchronization the waiting goroutine may *never* observe the write — the compiler can hoist the read out of the loop, or the CPU can cache a stale value. Using a channel or `atomic.Bool` establishes the happens-before edge that makes the write visible.
Quick Snippet
// BROKEN — data race, no happens-before relationship
var ready bool
var data string
func setup() { data = "loaded"; ready = true } // writes may be reordered
func wait() { for !ready {}; print(data) } // may see "" or never exit
// CORRECT — the channel send/receive synchronizes both writes
var done = make(chan struct{})
func setupOK() {
data = "loaded"
close(done) // send happens-before receive
}
func waitOK() {
<-done // after this, data's write is guaranteed visible
print(data) // always "loaded"
}Full Example
// Compile: go run memory_model.go
// Output:
// data is visible: loaded
//
// The channel close below establishes a happens-before edge: the write
// to `data` is guaranteed visible to the goroutine that receives from
// the channel. Without that synchronization the read could see "".
package main
import "fmt"
var data string
func main() {
done := make(chan struct{})
go func() {
data = "loaded"
close(done) // send/close happens-before the receive
}()
<-done // after this, data's write is guaranteed visible
fmt.Println("data is visible:", data)
}
go run memory_model.go