Lesson 12 / 25
sync.Once, sync/atomic and sync.Map
Smaller tools for specific jobs.
Choosing the right primitive
sync.Once runs a function exactly once, even when many goroutines call once.Do(f) simultaneously; the others wait until it has finished. It is ideal for lazy initialisation. Go 1.21 added the helpers sync.OnceFunc, sync.OnceValue and sync.OnceValues. The sync/atomic package offers lock-free operations; since Go 1.19 the typed values atomic.Int64, atomic.Bool, atomic.Pointer[T] and friends are the easiest way to use them. Atomics suit single counters and flags, but they do not make multi-field updates consistent; use a mutex for that. sync.Map is a concurrent map optimised for two cases described in its docs: keys written once and read many times, or goroutines working on disjoint key sets. For most other workloads, a plain map with a Mutex is simpler and type-safe.
Lazy config with OnceValue and an atomic counter
Two small primitives used together.
package main
import (
"fmt"
"sync"
"sync/atomic"
)
type Config struct{ Region string }
// loadConfig runs at most once, on first call, even under concurrency.
var loadConfig = sync.OnceValue(func() *Config {
fmt.Println("loading config")
return &Config{Region: "eu-west"}
})
var requests atomic.Int64
func handle() {
requests.Add(1)
_ = loadConfig().Region
}
func main() {
var wg sync.WaitGroup
for range 100 {
wg.Add(1)
go func() {
defer wg.Done()
handle()
}()
}
wg.Wait()
fmt.Println("requests:", requests.Load()) // 100; "loading config" printed once
}Do not mix atomic and plain access
If a variable is accessed atomically anywhere, every access must be atomic. A single plain read of a field that others update with atomic operations is a data race.
Quick check: Which use case does the sync.Map documentation describe as a good fit?
- Any map shared between goroutines
- Keys that are written once and read many times
- Maps whose values must be updated together in transactions
- Small maps accessed by a single goroutine
Answer
Keys that are written once and read many times — For general use a map plus a Mutex is usually simpler and type-safe.