stdatomic.h (C11) gives you atomic types and operations for lock-free concurrent programming. An atomic operation completes without interruption: no other thread can observe the object in a half-updated state. This is the C standard's portable answer to compiler-specific builtins like __sync_fetch_and_add and __atomic_compare_exchange.
If you have ever protected a shared counter with a mutex and thought “this seems like overkill for a single increment”, atomics are what you want instead. A mutex involves at least two system calls and kernel transitions; an atomic increment compiles to a single locked CPU instruction.
#include <stdatomic.h> atomic_int counter = 0; // from any thread: atomic_fetch_add(&counter, 1); // race-free increment int v = atomic_load(&counter); // race-free read atomic_store(&counter, 0); // race-free write
The standard atomic types are atomic_int, atomic_long, atomic_uint64_t, etc., or declared with _Atomic(type). The key operation for lock-free algorithms is compare-and-swap:
int expected = 5; bool ok = atomic_compare_exchange_strong(&x, &expected, 10); // if *x == expected: sets *x = 10, returns true // if *x != expected: writes current *x into expected, returns false
Every atomic operation has an optional memory order. The default (memory_order_seq_cst) is the strongest and most expensive. For performance-critical code, _explicit variants let you choose:
atomic_store_explicit(&flag, 1, memory_order_release); // publish a write int f = atomic_load_explicit(&flag, memory_order_acquire); // see it atomic_fetch_add_explicit(&x, 1, memory_order_relaxed); // just atomicity, no ordering
release/acquire pairs are sufficient for most producer-consumer patterns. relaxed gives only atomicity with no synchronisation between threads.
// compile: gcc -O2 -o atomicdemo atomicdemo.c -lpthread // run: ./atomicdemo // description: two threads increment a shared counter 1M times each; should total 2M #include <stdatomic.h> #include <pthread.h> #include <stdio.h> atomic_int counter = 0; void *worker(void *arg) { for (int i = 0; i < 1000000; i++) atomic_fetch_add_explicit(&counter, 1, memory_order_relaxed); return NULL; } int main(void) { pthread_t t1, t2; pthread_create(&t1, NULL, worker, NULL); pthread_create(&t2, NULL, worker, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); printf("counter = %d (expected 2000000)\n", atomic_load(&counter)); return 0; }
Run it: you will get exactly 2000000 every time. Now change atomic_fetch_add_explicit to a plain counter++ (a non-atomic read-modify-write), recompile without the atomic header, and run it a few times. You will get different results below 2000000 each time — that is the data race. The atomic version compiles to a single lock xadd instruction on x86, which is why it works without a mutex.