devutils/ATFQ/book ATFQ git

Part I · How it works

8.2 Race conditions, locks and atomics

alsorace conditionrace conditionsdata racemutexlocklocksdeadlockdeadlocksatomicatomicsthread safetythread-safecritical sectionmemory ordering

TL;DR

  • A race: the result depends on which thread runs first. count += 1 is read, add, write: two threads can read the same value and lose one increment.
  • A lock (mutex) lets one thread at a time into a critical section. Atomics do one small operation indivisibly, with no lock.
  • Races hide: the code works a thousand times, then fails under load or on a machine with more cores.
gcc -fsanitize=thread -g prog.c   # ThreadSanitizer finds data races
go test -race ./...

On your machine

alice@laptopexample
$ atfq 8.2
  CPUs      8 logical CPUs on 4 cores, up to 2 threads per core (SMT)

An example: Alice's laptop, one of the test machines. In your terminal, this block shows your own machine, read live.

Pitfalls

Deep dive 2 questions

Why can't threads just read and write shared variables?

Compilers and CPUs reorder memory operations for speed, as long as one thread can't tell. Another thread can: it may see a flag set before the data it guards. Locks and atomics include barriers that forbid harmful reorderings, and make each core's cached copy coherent at the right time (2.3). Without them, even "harmless" races are undefined behaviour in C, C++ and Rust.

How do languages help?

Rust refuses to compile data races: shared data must be behind a lock or an atomic. Go and Java define what racy code may observe, and ship race detectors. Message passing (channels, queues) avoids shared state altogether: one owner per piece of data, the others send it requests.

Commands
gdb
See also
8.1 Processes, threads and async: doing several things at once
2.3 CPU caches: why memory layout matters