C Mastery / Mutexes, Semaphores, Condition Variables
Part 10 — Concurrency

Mutexes, Semaphores, Condition Variables

This chapter covers the three fundamental synchronization primitives: mutexes, semaphores, and condition variables.

Why This Matters

These are the tools that make shared state safe. Mutexes provide mutual exclusion; semaphores count resources; condition variables let threads wait for a predicate. Nearly every concurrent program uses at least one.

Prerequisites

Core Concept

Mutex

A mutex allows only one thread in a critical section at a time:

mtx_lock(&m);
/* critical section */
mtx_unlock(&m);

Semaphore

A semaphore holds a non-negative count. wait decrements (blocks if zero); post increments. Used to control access to a fixed number of resources or to signal events.

Condition variable

A condition variable lets a thread wait (atomically releasing a mutex) until another thread signals that a predicate may be true:

mtx_lock(&m);
while (!predicate)
    cnd_wait(&cv, &m);
/* predicate holds */
mtx_unlock(&m);

The loop is essential: wakes can be spurious.

Examples

Mutex-protected counter (C11)

#include <threads.h>

static mtx_t m = MTX_INIT;
static int counter = 0;

int increment(void *arg)
{
    (void)arg;
    mtx_lock(&m);
    counter++;
    mtx_unlock(&m);
    return 0;
}

Condition variable (producer/consumer skeleton)

static mtx_t m = MTX_INIT;
static cnd_t cv = CND_INIT;
static int ready = 0;

int producer(void *arg)
{
    (void)arg;
    mtx_lock(&m);
    ready = 1;
    cnd_signal(&cv);
    mtx_unlock(&m);
    return 0;
}

int consumer(void *arg)
{
    (void)arg;
    mtx_lock(&m);
    while (!ready)
        cnd_wait(&cv, &m);
    /* consume */
    mtx_unlock(&m);
    return 0;
}

How It Works

A mutex blocks contending threads (via the OS, e.g., futex). A semaphore's count is an atomic integer; wait/post are atomic decrement/increment with blocking. A condition variable pairs a wait queue with a mutex: cnd_wait atomically releases the mutex and sleeps; cnd_signal wakes one waiter.

Variations

Semaphore vs. mutex

A binary semaphore can act like a mutex, but a mutex has an ownership concept (only the owner unlocks) and supports priority-inversion handling; a semaphore does not. Use a mutex for critical sections, a semaphore for counting/signaling.

Condition variable spurious wakeups

Always re-check the predicate in a loop; never assume a wake means the predicate is true.

Common Mistakes

Undefined Behavior

Portability

pthread_cond_t/sem_t. Semaphores are POSIX, not in C11 <threads.h>.

Under the Hood

Mutexes often use an atomic state plus a futex for blocking. Condition variables build on the mutex's wait queue. Semaphores use an atomic count and a wait queue.

Practical Usage

Exercises

1. Protect a shared counter with a mutex and verify correctness. 2. Implement a producer/consumer with a condition variable. 3. Implement a bounded buffer with a mutex and two condition variables. 4. (POSIX) Use a semaphore to limit concurrent access to a resource.

Deep Challenge

Implement a bounded (fixed-size) thread-safe queue with a mutex and two condition variables, and explain why the predicate loop is necessary even though the mutex is held.

References

Verification

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.conc.mutex06
c.conc.semaphore06
c.conc.condvar06