Go / Editorial solution guide
Diagnose Concurrent Map Access and Data Races
Establish one access policy for shared state and verify the repaired compound operation with race instrumentation.
Read-only knowledge base · No public comments
Recognize the symptoms
A program can report "fatal error: concurrent map writes", fail during map iteration, lose increments, or pass ordinary tests while -race reports conflicting accesses. The exact runtime symptom is not guaranteed. Concurrent-map failures are not a reliable recover-based control flow, and absence of a runtime failure does not establish race safety.
Understand the likely causes
- A plain map is shared by copied map values, struct fields, or returned aliases. A write overlapping another write or read needs synchronization. Protecting only assignments can leave a read-modify-write sequence unsafe. A final WaitGroup wait orders the final observer after workers, but does not order conflicting updates among those workers.
1. Trace all aliases and accesses
List every map read, write, range, delete, and returned view in the smallest synthetic reproduction. Identify which goroutines can overlap. A helper’s innocent lookup can race with another helper’s update. Do not assume a copied struct gives an independent map, and do not enable unsafe racing examples merely to obtain a preferred failure message.
2. Collect reports in an isolated build
Use go test -race on the relevant local package set or build a repaired synthetic command with -race on a supported platform. Preserve both conflicting stacks and their goroutine creation context. The detector observes exercised paths only; expand tests around the actual shared invariant. A runtime map error and a race report are evidence to investigate, not alternative correctness specifications.
3. Protect the complete update
The complete example uses one stable mutex for each increment’s lookup and assignment, adds workers before launch, and joins before reporting. All access follows the same rule. This fixes the compound operation rather than merely moving a crash. A component should keep its map private so callers cannot bypass synchronization through an escaped alias.
package main
import (
"fmt"
"sync"
)
func main() {
values := map[string]int{"count": 0}
var mu sync.Mutex
var workers sync.WaitGroup
workers.Add(2)
for i := 0; i < 2; i++ {
go func() {
defer workers.Done()
for n := 0; n < 1000; n++ {
mu.Lock()
values["count"]++
mu.Unlock()
}
}()
}
workers.Wait()
mu.Lock()
final := values["count"]
mu.Unlock()
fmt.Println("count", final)
}
count 2000
The mutex covers every conflicting update, producing the exact joined count of 2000.
4. Choose locking or exclusive ownership
A mutex is often clearest for related fields. Alternatively, a dedicated owner goroutine can process all map operations through a bounded request protocol. Immutable snapshots can support read-heavy designs only if publication is synchronized and snapshots remain immutable. sync.Map is specialized and does not automatically make multi-key invariants transactional. Choose a design that callers can follow consistently.
5. Review escaping data and copied locks
Do not copy a mutex-bearing object after use or use value receivers that copy its lock. Return a copied snapshot when callers need independent access, and sort keys if deterministic presentation is required. A snapshot must be taken while the relevant lock protects the source. If values contain slices or maps, define whether the snapshot is shallow or recursively independent.
Verify the fix
The complete example must report count 2000 in ordinary and supported race builds. Require no race report and verify the expected count, then test concurrent reads, updates, snapshots, and shutdown through the public API. A clean detector run covers its exercised schedules; retain the written access policy and inspect all aliases.
Keep it from recurring
- Keep shared maps private, guard entire invariants, and document whether methods are concurrent-safe. Add WaitGroup work before launch, avoid copied locks, and run race-aware tests regularly on supported systems. Treat deterministic ordering separately from map safety.