C Mastery / stdatomic.h and threads.h: ISO C Concurrency Primitives
Part 3 — The Standard Library

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

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

Undefined Behavior

Portability

uneven (MSVC lacks <threads.h>).

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

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.

References

Verification

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.lib.atomic05
c.lib.threads05