Data Races

When two threads try to use the same variable without taking turns.

The idea

A Data Race occurs when two threads access the exact same memory location concurrently, and at least one of them is writing to it. Because threads can be paused by the OS at any given microsecond, the final result depends entirely on the unpredictable timing (the "race") of the threads. This leads to Heisenbugs—bugs that disappear when you try to observe them.

Step 1: Thread A is supposed to withdraw $10. Thread B is supposed to deposit $20.

How it works (Mutexes / Locks)

The solution to a data race is Mutual Exclusion (Mutex). You place a lock around the shared variable. Before a thread can read or write the variable, it must acquire the lock. If another thread has the lock, it must wait its turn.

# Go example: Fixing a data race with a Mutex

import "sync"

var balance int = 100
var mu sync.Mutex // The Lock

func withdraw(amount int) {
    mu.Lock()         // 1. Grab the lock
    balance -= amount // 2. Safely modify
    mu.Unlock()       // 3. Let others have a turn
}

func deposit(amount int) {
    mu.Lock()
    balance += amount
    mu.Unlock()
}

Cost

Locks eliminate data races, but they introduce contention. If 1,000 threads all need to update `balance`, they must form a single-file line. Your beautiful multi-core CPU is suddenly executing code sequentially, destroying performance.

Watch out for