Go

What happens when multiple goroutines access a map concurrently? How do you make it safe?

Question 59HardGo 1.22 to 1.25

Built-in maps are not safe for concurrent use if at least one goroutine writes. Concurrent reads alone are fine. The runtime has a cheap best-effort check (a hashWriting flag). When it catches a conflict it calls fatal("concurrent map writes") or reports "concurrent map read and map write". This is not a panic, so recover cannot catch it. The whole process crashes. Races the check misses can silently corrupt the map, so run tests with go test -race.

type SafeCounter struct {
	mu sync.RWMutex
	m  map[string]int
}

func (c *SafeCounter) Inc(k string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.m[k]++
}

func (c *SafeCounter) Get(k string) (int, bool) {
	c.mu.RLock()
	defer c.mu.RUnlock()
	v, ok := c.m[k]
	return v, ok
}

Options, ranked:

  1. sync.Mutex/RWMutex plus a map: the default. RWMutex only helps when reads dominate and critical sections are not trivially short.
  2. Sharded maps: N maps, each with its own mutex, chosen by hash(key) % N. This cuts contention under heavy write load.
  3. sync.Map: for specific access patterns (see the next question).
  4. Confinement: a single owning goroutine that serves requests over channels.

Never copy a struct that contains a mutex; go vet catches this. Returning a map that the lock guards also leaks the race to callers. Return a maps.Clone instead.

More on Arrays, Slices, Maps & Strings

All 37 Arrays, Slices, Maps & Strings questions