stdatomic.h and threads.h: ISO C Concurrency Primitives
This chapter introduces the ISO C11 concurrency facilities: <stdatomic.h> (atomics) and <threads.h> (threads, mutexes, condition variables). Full concurrency theory is in Part 10; this chapter covers the standard interface.
Why This Matters
C11 added optional support for threads and atomics directly to the language. These are the portable, standard alternative to platform thread APIs, though their availability varies (notably, MSVC does not support <threads.h>).
Prerequisites
c.stdlib.1— library overview.
Core Concept
Atomics
_Atomic is a type qualifier/specifier, and <stdatomic.h> provides atomic types and operations:
#include <stdatomic.h>
atomic_int counter = ATOMIC_VAR_INIT(0); /* or just 0 in C17 */
atomic_fetch_add(&counter, 1);
int v = atomic_load(&counter);
Key operations: atomic_load, atomic_store, atomic_fetch_add, atomic_compare_exchange_weak/strong, atomic_exchange. They take an optional memory-order argument (memory_order_relaxed, memory_order_acquire, memory_order_release, memory_order_acq_rel, memory_order_seq_cst).
Threads
<threads.h> provides thrd_t, thrd_create, thrd_join, mtx_t, cnd_t, mtx_lock, mtx_unlock, cnd_wait, cnd_signal, and more.
#include <threads.h>
int worker(void *arg) { return 0; }
int main(void)
{
thrd_t t;
thrd_create(&t, worker, NULL);
thrd_join(t, NULL);
return 0;
}
Syntax
#include <threads.h>
#include <stdatomic.h>
atomic_int counter = 0;
mtx_t lock;
Examples
Atomic counter
#include <stdio.h>
#include <stdatomic.h>
int main(void)
{
atomic_int counter = 0;
atomic_fetch_add(&counter, 1);
printf("%d\n", atomic_load(&counter));
return 0;
}
Expected output: 1.
Thread with a mutex
#include <threads.h>
#include <stdio.h>
static mtx_t lock;
static int shared = 0;
int worker(void *arg)
{
(void)arg;
mtx_lock(&lock);
shared++;
mtx_unlock(&lock);
return 0;
}
int main(void)
{
mtx_init(&lock, mtx_plain);
thrd_t t;
thrd_create(&t, worker, NULL);
worker(NULL);
thrd_join(t, NULL);
mtx_destroy(&lock);
printf("%d\n", shared);
return 0;
}
Expected output: 2.
How It Works
Atomics guarantee that individual operations are indivisible and (with the right memory order) that other memory operations are ordered correctly. Mutexes provide mutual exclusion by blocking threads that try to lock an already-locked mutex. Condition variables allow waiting for a predicate to become true.
Variations
Memory orders
The memory-order argument controls how strong the ordering guarantee is. memory_order_seq_cst (the default) is the strongest; memory_order_relaxed is the weakest. Full semantics in c.conc.6.
Platform alternatives
On POSIX, <pthread.h> is the more featureful equivalent. On Windows, use the Win32 thread API. <threads.h> is a portable but thinner layer.
Common Mistakes
- Forgetting to initialize/destroy mutexes and condition variables.
- Using
memory_order_relaxedwhen stronger ordering is needed. - Assuming atomics make a whole multi-step sequence atomic (they do not).
- Forgetting that
<threads.h>is optional in C11.
Undefined Behavior
- Data races on non-atomic objects.
VERIFIED - Destroying a mutex/condition variable while it is in use.
- Using an uninitialized atomic or thread object.
Portability
<stdatomic.h>and<threads.h>are C11, but their support is optional and
uneven (MSVC lacks <threads.h>).
- The memory model is standard; the underlying implementation is
platform-specific.
Under the Hood
Atomics compile to hardware atomic instructions (e.g., lock xadd on x86, ldxr/stxr on ARM) with appropriate memory barriers. Mutexes are implemented with OS futexes or equivalent blocking primitives.
Practical Usage
- Prefer
_Atomicand<stdatomic.h>for simple shared counters and flags. - Use mutexes for protecting larger critical sections.
- Use condition variables for producer/consumer patterns.
- On platforms without
<threads.h>, use POSIX/Win32 APIs.
Exercises
1. Write a program that increments an atomic counter from two threads. 2. Use a mutex to protect a shared data structure. 3. Use a condition variable to implement a simple producer/consumer. 4. Compare memory_order_relaxed and memory_order_seq_cst behavior.
Deep Challenge
Implement a simple lock-free counter using <stdatomic.h> compare-and-swap and explain the memory-order choices. Then discuss why this is (or is not) correct under the C memory model.
Related Concepts
c.conc.5— atomics and the memory model.c.conc.3— mutexes and condition variables.c.ptr.volatile— why volatile is not atomic.
References
- ISO/IEC 9899:2018 §7.17 (stdatomic.h), §7.26 (threads.h).
Verification
_Atomicand memory orders are C11.VERIFIED- Data races on non-atomic objects are UB.
VERIFIED - No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] atomic types and operations
- [ ] memory_order options
- [ ] thrd_create/thrd_join
- [ ] mtx_t and cnd_t
- [ ] Availability/portability
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.lib.atomic | 0 | 5 |
| c.lib.threads | 0 | 5 |