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.