← Concepts

阅读中文版本

Data Races

A data race is two unsynchronized accesses to the same memory where at least one is a write — undefined behavior in most language models, distinct from higher-level race conditions.

concurrency

Definition

A data race is the lowest-level synchronization failure: conflicting memory accesses from different threads without an ordering relation between them. Languages classify it as undefined behavior (C++, Rust with unsafe) or bound it with weak guarantees (Java, Go). It differs from race conditions — logical timing bugs — which can exist even in race-free programs.

Context and scope

Detected with ThreadSanitizer, Java race detectors, and loom fuzzing. Explains why unlocked read-modify-write counters and unsynchronized publication corrupt state.

Common misconceptions

  • Data races cause wrong values occasionally — undefined behavior is broader: register-cached reads, torn writes, and compiler assumptions can corrupt unrelated code
  • Atomic types fix data races — correct synchronization fixes them; an atomic on the wrong variable leaves the race on the other one
  • Races only matter on multi-core — single-core preemptive threading races through context switches and compiler reordering just the same

Anti-patterns

  • Incrementing shared counters with read-modify-write instead of atomic operations
  • Publishing object references before all fields are written and relying on luck
  • Testing under low concurrency and shipping races that only interleave under production scheduling

Related concepts

Related examples

Authoritative source

https://en.wikipedia.org/wiki/Race_condition

What it is

Two accesses conflict when they target the same location and one writes. Without happens-before ordering between them, the program has a data race. The consequences range from stale reads to torn 64-bit values on 32-bit platforms to whole-program undefined behavior in strict languages.

How it works

Compilers cache values in registers, fuse and reorder accesses, and delete writes they can prove redundant under single-thread reasoning. CPUs add store buffering and relaxed visibility. Detectors work by shadow-tracking access orderings and flagging unordered conflicting pairs — ThreadSanitizer catches races that ran but did not yet corrupt anything.

When to hunt

Any shared mutable state touched from callbacks, timers, and multiple requests. Make sharing explicit — pass ownership, use channels, or guard with one lock — and run race detectors in CI where the runtime supports it.

Pitfalls

Races hide behind happens-lucky on strongly ordered x86. Fixing the wrong variable with atomic leaves the race intact on its twin field. Some fixes introduce deadlocks instead: synchronize one field and wait on another. High-level race conditions — check-then-act sequences — remain even after every data race is gone.