SkillByAIOpen interactive version →

Lesson 17 / 26

Data Races, Mutexes and the Race Detector

Shared memory needs synchronisation.

Find races with -race

A data race happens when goroutines access the same variable concurrently and at least one writes, without synchronisation; results become unpredictable. Protect shared state with a sync.Mutex, use atomic operations for simple counters, or share by communicating over channels instead. Go's race detector (go run -race, go test -race) instruments memory accesses and reports races at runtime with stack traces; run your tests with it in CI.

Unsynchronised versus mutex-protected counters, run

I ran this with Go 1.27.1 (go run -race . > run.log 2>&1 in a module named demo, standard library only). The race detector reports "WARNING: DATA RACE" for the unprotected counter and the program exits with status 66, while the mutex-protected counter correctly reaches 100. Output is trimmed to the key lines.

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	counter := 0
	for range 100 {
		wg.Go(func() { counter++ }) // unsynchronised write from many goroutines
	}
	wg.Wait()

	var mu sync.Mutex
	safe := 0
	for range 100 {
		wg.Go(func() {
			mu.Lock()
			safe++
			mu.Unlock()
		})
	}
	wg.Wait()
	fmt.Println("safe counter:", safe)
}

Output:

WARNING: DATA RACE
safe counter: 100
exit status 66

Run tests with -race in CI

The detector only finds races in code that actually runs, so combine it with good test coverage.

Quick check: What is a data race?

  • A slow network request
  • Two goroutines reading the same constant
  • Concurrent access to the same memory with at least one write and no synchronisation
  • A channel with a buffer
Answer

Concurrent access to the same memory with at least one write and no synchronisation — Use mutexes, atomics or channels.