Synchronization and race-free state
Mutex ownership, atomic operations, coherent snapshots and zero-value usability
A data race occurs when goroutines access the same memory concurrently, at least one access writes, and there is no sufficient synchronization. Race-free execution is a correctness requirement, not only a performance concern. Concurrent writes to a map can crash; even scalar increments like count++ are a read-modify-write sequence that can lose updates without coordination.
var mu sync.Mutex
count := 0
mu.Lock()
count++
mu.Unlock()Protect an invariant, not an isolated line
A mutex guards a collection of state and the relationships between its fields. Decide which lock owns every field, then acquire it for every access that participates in the invariant. Locking only writes while reads remain unprotected is still a race. A snapshot needs a coherent view of the complete map, not a sequence of independently synchronized reads that can represent different instants.
The zero value of sync.Mutex is usable. A map inside a zero-value service is nil, so initialize it while holding the same lock before its first write. Do not copy a mutex after use: copying a struct containing one can create independent locks protecting the same referenced data, or copy locked state. Use pointer receivers and return snapshots without embedded locks. RWMutex can allow concurrent readers, but it adds policy and overhead; choose it based on workload and measurement.
Atomic operations suit independent numeric or pointer state and particular lock-free protocols. They do not make a multi-key map update atomic or automatically preserve a cross-field invariant. Channels can instead transfer ownership to a single state-owning goroutine; that design still needs request/reply and shutdown lifetimes. Prefer the clearest correct ownership model before optimizing contention.
Snapshots and drains have linearization points
A method is atomic in this exercise if callers can regard its effect as happening at one instant between call and return. Drain reads all current counts and resets them in the same critical operation, so increments cannot vanish between a read and a later reset. A returned snapshot or drained map belongs to the caller and must not share future service storage. Negative deltas are allowed and still preserve exact per-key arithmetic.
Test with a start barrier and WaitGroup so many writers are active, and assert conservation of all submitted increments across concurrent drains. Do not rely on sleeping for an assumed interleaving. The race detector instruments accesses on exercised paths and is valuable evidence; it does not prove linearizability by itself, which is why behavioral totals and snapshot isolation remain necessary. Performance analysis should then measure contention and determine whether sharding or a state-owning goroutine is warranted.
Put it into practice
Implement the exercise in a full-screen workspace, then run its tests.