Concurrency · 3 MIN READ
Atomic publication is safe only when the published snapshot stays immutable
Publish one consistent snapshot and preserve ownership of its reachable maps and slices.
An atomic load or store synchronizes an operation on the atomic value. It does not recursively make the object graph immutable or protect a later map mutation. A useful read-mostly pattern builds a complete independent configuration, publishes one pointer and lets readers hold that immutable snapshot. All fields that must agree belong to the same published value.
concurrency · A MAP OF THE IDEA
- Build independent state
- Publish one pointer
- Read one snapshot
- Never mutate published graph
Useful when
- Publish one consistent snapshot and preserve ownership of its reachable maps and slices.
- Makes the owner and the failure boundary explicit.
Watch out
- Publishing a pointer and then changing its map races with readers.
- Run native service and failure tests in your own development environment.
Implementation & operation cost: Additional state and tests must be justified by the demonstrated failure; this lesson is not a requirement for every Go program.
The contract and the failure
An atomic load or store synchronizes an operation on the atomic value. It does not recursively make the object graph immutable or protect a later map mutation. A useful read-mostly pattern builds a complete independent configuration, publishes one pointer and lets readers hold that immutable snapshot. All fields that must agree belong to the same published value.
Work through the example
Choose a mutex when operations update related state together; choose an atomic primitive only for a precisely defined protocol. Atomic types and mutexes must not be copied after first use. If a snapshot contains slices, pointers or nested maps, define deep-enough copying and lifetime rules. Compare this with sync.Map, whose per-operation contracts do not provide a transaction across arbitrary keys.
package main
import (
"fmt"
"sync/atomic"
)
type Config struct{ limits map[string]int }
func publish(dst *atomic.Pointer[Config], src map[string]int) {
copied := make(map[string]int, len(src))
for k, v := range src {
copied[k] = v
}
dst.Store(&Config{limits: copied})
}
func main() {
var current atomic.Pointer[Config]
input := map[string]int{"workers": 4}
publish(¤t, input)
input["workers"] = 99
snapshot := current.Load()
fmt.Println(snapshot.limits["workers"]) // 4
}The map is copied before the pointer is stored. Changing the caller’s input afterward does not mutate the published configuration. Readers load one pointer and keep that snapshot, rather than loading separate fields at different times. A production API should avoid exposing a mutable map from the snapshot to callers.
Production decisions
Publishing a pointer and then changing its map races with readers. Two independent atomic fields do not automatically form a consistent pair. Race detection finds executed data races, not every logical ordering error. Test concurrent publication/read behavior and business invariants with native go test -race; the app sandbox does not supply a native race detector.
Explain it in an interview
After storing *Config atomically, may a writer mutate its map while readers use it? No. Atomic publication protects the pointer protocol, not later mutations of reachable state; publish independent immutable data or use a lock. Describe the state before the operation, its owner, the failure between steps and the evidence you would test. Distinguish what this example demonstrates from the infrastructure a production service still needs.
TAKE THIS WITH YOU
Publish one consistent snapshot and preserve ownership of its reachable maps and slices. No. Atomic publication protects the pointer protocol, not later mutations of reachable state; publish independent immutable data or use a lock.
Check your understanding
After storing *Config atomically, may a writer mutate its map while readers use it?
Go to the source
Progress stays in this browser. No account needed.