Lesson 22 / 25

Data Races and the Race Detector

Unsynchronised access and how to catch it.

What counts as a data race

A data race happens when two goroutines access the same memory location concurrently, at least one access is a write, and nothing orders them (no channel operation, lock or atomic establishes a happens-before relationship under the Go memory model). Races cause lost updates, torn reads and, with maps, a runtime crash (concurrent map writes). Go ships a race detector: build or test with -race. It instruments memory accesses and reports races that actually occur during that run, with stack traces for both accesses, so it has no false positives but can miss races on code paths the run did not exercise. It makes programs noticeably slower and more memory-hungry, so run it in CI and in tests, and optionally in a canary, not across all production traffic. It needs cgo on some platforms; check the docs for supported systems.

Finding concurrency bugs before users do

Races, leaks and deadlocks are found with the race detector, goroutine profiles, leak checkers and deterministic tests.

Four ideas: data races, leaks and deadlocks, testing, and a final checklist.
Figure 8.1: detect, diagnose, test and review.

A racy counter and the commands to catch it

counter++ is a read-modify-write, not an atomic operation.

// counter_test.go
package counter

import (
	"sync"
	"testing"
)

func TestCounter(t *testing.T) {
	var wg sync.WaitGroup
	count := 0
	for range 100 {
		wg.Add(1)
		go func() {
			defer wg.Done()
			count++ // DATA RACE: unsynchronised read-modify-write
		}()
	}
	wg.Wait()
	if count != 100 {
		t.Errorf("count = %d", count) // may pass or fail; -race flags it
	}
}

// Fix: protect count with a sync.Mutex, or use an atomic.Int64.
//
// Commands (shell):
//   go test -race ./...
//   go run -race .
//   go build -race -o app-race .

Make -race part of CI

Run go test -race ./... on every change. Pair it with tests that really exercise concurrency (several goroutines, t.Parallel()), because the detector only sees races that happen during the run.

Quick check: Which statement about the Go race detector is accurate?

  • It detects deadlocks rather than races
  • It proves a program is race-free
  • It runs at no extra cost, so it should always be on in production
  • It reports only races that actually happen during the instrumented run
Answer

It reports only races that actually happen during the instrumented run — No false positives, but no guarantee either: coverage matters.