30.2 / 30.2.1 — Condition Variables: Introduction
The problem with busy-waiting — and how condition variables fix it
What Problem Does a Condition Variable Solve?
Mutexes alone are not enough for all synchronization needs. Sometimes a thread does not just need exclusive access — it needs to wait for something to happen before it can proceed.
Example: A consumer thread can only work when a producer thread has made something available. Without condition variables, the consumer must sit in a loop constantly checking:
/* BAD: Busy-waiting wastes 100% of CPU */
while (avail == 0) {
/* do nothing — just keep checking */
}
This is called busy-waiting or a spin loop. It wastes the entire CPU — the thread is “awake” doing nothing useful, preventing other threads from running.
A condition variable solves this: the waiting thread goes to sleep (releases the CPU) and is woken up only when another thread signals that the condition has changed.
❌ Without Condition Variable
Consumer loops endlessly checking avail. Thread is always “running” but doing nothing useful. Wastes CPU, slows other threads, generates heat.
✅ With Condition Variable
Consumer calls pthread_cond_wait() and goes to sleep. CPU is free for other work. Producer calls pthread_cond_signal() to wake the consumer exactly when needed.
The Busy-Wait Anti-Pattern (Code to Avoid)
Here is what a producer-consumer looks like without condition variables — the wrong way that wastes CPU:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static int avail = 0; /* Number of items ready for consumption */
/* Producer: creates items and signals availability */
void *producer(void *arg)
{
for (int i = 0; i < 5; i++) {
/* Produce an item (simulate with sleep) */
struct timespec ts = {1, 0};
nanosleep(&ts, NULL);
pthread_mutex_lock(&mtx);
avail++;
printf("Producer: avail = %d\n", avail);
pthread_mutex_unlock(&mtx);
}
return NULL;
}
/* Consumer: BAD VERSION — busy-waits, wastes CPU */
void *consumer_bad(void *arg)
{
int total_consumed = 0;
while (total_consumed < 5) {
/* This loop runs millions of times wasting CPU */
pthread_mutex_lock(&mtx);
if (avail > 0) {
avail--;
total_consumed++;
printf("Consumer: consumed item %d\n", total_consumed);
}
pthread_mutex_unlock(&mtx);
/* No sleep — immediately tries again even if avail==0 */
}
return NULL;
}
int main(void)
{
pthread_t prod, cons;
pthread_create(&prod, NULL, producer, NULL);
pthread_create(&cons, NULL, consumer_bad, NULL);
pthread_join(prod, NULL);
pthread_join(cons, NULL);
return 0;
}
/*
* This works but the consumer burns 100% CPU spinning.
* On a multicore system this wastes a whole core.
* On a single core, it competes with the producer for CPU time.
* The fix is condition variables (see ch30-08-condvar-signal-wait.html)
*/
1. CPU waste — the spinning thread uses 100% of a CPU core doing nothing useful.
2. Power waste — important in embedded/mobile systems with battery constraints.
3. Starves other threads — on single-CPU systems, the spinning thread may prevent the producer from running.
4. Poor scalability — as the system gets busier, spin loops make things worse.
30.2.1 — Statically Allocated Condition Variables
Just like a mutex, a condition variable has its own type and must be initialized before use.
/* Static initialization (like PTHREAD_MUTEX_INITIALIZER for mutexes) */ pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
The Relationship Between Condition Variable and Mutex
A condition variable is always used together with a mutex. They work as a pair:
| Mutex role | Protects the shared variable from concurrent access (mutual exclusion) |
| Condition variable role | Lets threads efficiently sleep until the shared variable reaches a desired state, and wake up when it does |
The term “signal” used with condition variables is not related to Unix signals (SIGTERM, SIGKILL etc.). It simply means “notify” or “indicate” — to wake up a sleeping thread.
Code Example 2: Declaring a Mutex + Condition Variable Pair
#include <pthread.h>
#include <stdio.h>
/* Standard pattern: always declare cvar and mutex together */
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
/* The shared state that the condition variable protects */
static int data_ready = 0; /* 0 = not ready, 1 = ready */
static int data_value = 0;
/* Producer: sets data_ready and signals the condition */
void *producer(void *arg)
{
struct timespec ts = {2, 0};
nanosleep(&ts, NULL); /* Simulate producing data */
pthread_mutex_lock(&mtx);
data_value = 42;
data_ready = 1; /* Set the shared state */
pthread_mutex_unlock(&mtx);
pthread_cond_signal(&cond); /* Wake the waiting consumer */
printf("Producer: signaled, data = %d\n", data_value);
return NULL;
}
/* Consumer: waits for data_ready to become 1 */
void *consumer(void *arg)
{
pthread_mutex_lock(&mtx);
/* Sleep until data_ready == 1 */
while (data_ready == 0)
pthread_cond_wait(&cond, &mtx); /* Atomically unlocks mtx and sleeps */
/* Now data is ready */
printf("Consumer: woke up, data = %d\n", data_value);
pthread_mutex_unlock(&mtx);
return NULL;
}
int main(void)
{
pthread_t prod, cons;
pthread_create(&cons, NULL, consumer, NULL); /* Start consumer first */
pthread_create(&prod, NULL, producer, NULL);
pthread_join(prod, NULL);
pthread_join(cons, NULL);
return 0;
}
/*
* Compile: gcc -o condvar_intro condvar_intro.c -lpthread
* Output:
* Producer: signaled, data = 42
* Consumer: woke up, data = 42
*
* Consumer does NOT spin — it sleeps until woken.
*/
Interview Questions — Condition Variables Introduction
pthread_cond_t. It is initialized statically with PTHREAD_COND_INITIALIZER or dynamically with pthread_cond_init().