# Data race

A data race is two accesses to the same memory location from different threads, at least one of them a write, with nothing ordering one before the other. C11 states it as two conflicting actions in different threads, at least one not atomic, where neither happens before the other, and makes the result undefined behaviour. Golang's memory model says a write concurrent with another read or write of the same location, unless all the accesses go through `sync/atomic`.

The definition is about ordering, not about bugs. A program with a data race may pass every test, as the counter example in [[threadsanitizer-limits]] does until `N=10000`, but it contains a latent bug whether or not a test notices.

## Data race versus general race

A general race (race condition) is a correctness bug that depends on scheduling. The two overlap without being the same thing. The standard illustration is a read-modify-write on a counter, `v = counter; counter = v + 1`. Without a lock it is a data race and a general race. Put the read and the write in two separate critical sections and the data race is gone, because every access is now ordered by the mutex, but increments are still lost. No data-race detector reports that version, because there is nothing for it to report.

This distinction is why tools built for one leave a large share of concurrency bugs untouched. In Tu et al's study of real Golang bugs (see [[message-passing-shared-mutable-state]]) the race detector caught roughly half of the non-blocking bugs. Channels remove data races without removing coordination bugs, which is the argument in [[message-passing-vs-shared-memory]] and the taxonomy in [[go-channel-bug-patterns]].

## How detectors find them

Data races can in principle be detected automatically, with no annotations from the programmer. Dynamic detectors instrument every memory access and the synchronization primitives, then check each observed execution.

Lockset detectors, starting with Eraser (1997), record which locks are held on each access to a shared location and report when the intersection of those sets becomes empty. They catch races that the observed interleaving happened not to expose, but they don't understand ordering idioms like thread join, which produces false positives, and they cannot see a race in code that takes no locks at all.

Happens-before detectors compute the ordering relation directly. FastTrack gives each thread a logical clock and a vector clock of what it knows about the others, merges vectors on acquire and publishes them on release, and flags an access whose last conflicting access isn't covered by the current thread's vector. ThreadSanitizer v2 and v3 work along these lines and are what Clang, GCC, Golang, Swift and OCaml use.

A happens-before detector only knows the orderings the run actually created, and that is its main weakness. An unrelated mutex taken in a lucky order creates an edge between two unsynchronized writes. TSan also bounds its own memory with fixed budgets (255 thread slots, 14-bit clocks, four access records per 8-byte granule), and Golang's `sync.Pool` deliberately feeds the detector synthetic orderings. [[threadsanitizer-limits]] shows each one hiding an obvious race in C and Golang.

## Why language models forbid them

In [[shared-memory-consistency-causality]] the absence of data races is the condition that makes a simple memory model possible: sequential consistency for atomic operations holds provided non-atomic accesses are data-race-free (SC+DRF), which Java and Golang rely on. An unsynchronized access is a side channel through which the hardware's real, weaker ordering becomes observable. That is also why the C and C++ standards classify data races as undefined behaviour instead of assigning them a result: a defined result would require every architecture to hide a lack of [[write-atomicity]].

## Related

- [[threadsanitizer-limits]] - a working FastTrack-style detector and the TSan budgets that miss races
- [[shared-memory-consistency-causality]] - what "happens before" means at the cache-coherence level
- [[message-passing-vs-shared-memory]] - what giving up shared memory does and doesn't remove
- [[gosentry-go-fuzzing-fork]] - fuzzing with race detection enabled
