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
c.conc.2— data races.
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
- Unlocking a mutex not held (UB) or unlocking in a different thread.
- Forgetting the
whileloop aroundcnd_wait. - Using a semaphore where a mutex is correct (or vice versa).
- Forgetting to initialize/destroy the primitives.
Undefined Behavior
- Unlocking an unheld mutex.
VERIFIED - Destroying a mutex/cv while in use.
VERIFIED - Data races on the shared predicate (must be protected by the mutex).
Portability
- C11
<threads.h>providesmtx_t/cnd_t; POSIX providespthread_mutex_t/
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
- Use a mutex for mutual exclusion.
- Use a condition variable for "wait until predicate is true."
- Use a semaphore for resource counting and signaling.
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.
Related Concepts
c.conc.4— rw-locks and spinlocks.c.conc.9— deadlock.c.stdlib.12— threads.h.
References
- ISO C §7.26 (threads.h), POSIX threads/semaphores.
Verification
- Primitive semantics are standard/POSIX.
VERIFIED - Unlocking an unheld mutex is UB.
VERIFIED - Spurious wakeups require predicate loops.
VERIFIED - No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Mutex lock/unlock
- [ ] Semaphore wait/post
- [ ] Condition variable wait/signal
- [ ] Predicate loop
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.conc.mutex | 0 | 6 |
| c.conc.semaphore | 0 | 6 |
| c.conc.condvar | 0 | 6 |