30.2.2 — Signaling and Waiting on Condition Variables
pthread_cond_signal, broadcast, wait, timedwait — the complete producer-consumer pattern
The Four Condition Variable Functions
/* Wake ONE waiting thread */ int pthread_cond_signal(pthread_cond_t *cond);
/* Wake ALL waiting threads */ int pthread_cond_broadcast(pthread_cond_t *cond);
/* Sleep until cond is signaled (atomically unlock mutex while sleeping) */ int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
/* Same as wait but with a timeout */ int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
/* All return 0 on success, or a positive error number on error */
pthread_cond_wait() — The Heart of the Pattern
pthread_cond_wait() does three things atomically (as one indivisible unit):
pthread_cond_signal() or pthread_cond_broadcast() on this condition variable.If “unlock mutex” and “go to sleep” were two separate steps, there would be a window where: the mutex is unlocked, but the thread has not yet started sleeping. The producer could run, change the state, and call
signal() — but the consumer is not yet sleeping so the signal is lost. The consumer then sleeps forever waiting for a signal that already happened. Making steps 1 and 2 atomic eliminates this race condition.signal() vs broadcast() — Which One to Use?
pthread_cond_signal()
Wakes at least one waiting thread. Use when all waiting threads do the same job and only one needs to run (e.g., one consumer to consume one item). More efficient than broadcast when this assumption holds.
pthread_cond_broadcast()
Wakes all waiting threads. Use when different threads might be waiting for different conditions on the same variable, or when you want to be safe and wake everyone. All awakened threads that have no work to do will simply go back to sleep.
while loop (covered in section 30.2.3), threads woken unnecessarily by broadcast will simply re-test the condition, find it false, and go back to sleep. The cost is some extra context switching.The Standard Wait Pattern
The correct pattern for a thread waiting on a condition variable always looks like this:
pthread_mutex_lock(&mtx);
while (/* shared variable NOT in desired state */)
pthread_cond_wait(&cond, &mtx);
/* Now shared variable IS in desired state — do work */
pthread_mutex_unlock(&mtx);
The reason for while instead of if is covered in detail in section 30.2.3 (spurious wakeups). For now, just remember: always use while.
Code Example 1: Complete Producer-Consumer with Condition Variable
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static int avail = 0; /* Number of items ready to consume */
#define NUM_ITEMS 10
/* Producer: produces items one at a time */
void *producer(void *arg)
{
for (int i = 1; i <= NUM_ITEMS; i++) {
/* Simulate work to produce one item */
struct timespec ts = {0, 200000000}; /* 200ms */
nanosleep(&ts, NULL);
/* Add the item */
pthread_mutex_lock(&mtx);
avail++;
printf("Producer: produced item %d (avail=%d)\n", i, avail);
pthread_mutex_unlock(&mtx);
/* Signal the consumer that something is available */
pthread_cond_signal(&cond);
}
return NULL;
}
/* Consumer: waits for items and consumes them */
void *consumer(void *arg)
{
int total = 0;
while (total < NUM_ITEMS) {
pthread_mutex_lock(&mtx);
/* Sleep while nothing is available */
while (avail == 0)
pthread_cond_wait(&cond, &mtx);
/* Consume all available items */
while (avail > 0) {
avail--;
total++;
printf("Consumer: consumed item %d (avail=%d)\n", total, avail);
}
pthread_mutex_unlock(&mtx);
}
printf("Consumer: done, total consumed = %d\n", total);
return NULL;
}
int main(void)
{
pthread_t prod, cons;
pthread_create(&cons, NULL, consumer, NULL);
pthread_create(&prod, NULL, producer, NULL);
pthread_join(prod, NULL);
pthread_join(cons, NULL);
return 0;
}
/*
* Compile: gcc -o producer_consumer producer_consumer.c -lpthread
*
* Consumer SLEEPS while avail==0 (no CPU waste).
* Producer wakes it up exactly when an item is ready.
*/
Signal Timing: Before or After Unlock?
In the producer code above, we unlock the mutex first, then signal. POSIX allows either order:
- Unlock then signal (recommended) — the waiting thread won’t immediately block on the mutex when it wakes up. Slightly better performance.
- Signal then unlock — also correct. Some implementations use “wait morphing” to handle this efficiently.
pthread_cond_timedwait() — Waiting With a Deadline
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <pthread.h>
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static int data_ready = 0;
void *consumer_with_timeout(void *arg)
{
struct timespec deadline;
pthread_mutex_lock(&mtx);
while (data_ready == 0) {
/* Compute deadline: 3 seconds from now */
clock_gettime(CLOCK_REALTIME, &deadline);
deadline.tv_sec += 3;
int s = pthread_cond_timedwait(&cond, &mtx, &deadline);
if (s == ETIMEDOUT) {
printf("Consumer: timed out waiting for data!\n");
pthread_mutex_unlock(&mtx);
return NULL;
}
}
printf("Consumer: got data!\n");
pthread_mutex_unlock(&mtx);
return NULL;
}
void *slow_producer(void *arg)
{
/* This producer takes 5 seconds — will cause consumer timeout */
struct timespec ts = {5, 0};
nanosleep(&ts, NULL);
pthread_mutex_lock(&mtx);
data_ready = 1;
pthread_mutex_unlock(&mtx);
pthread_cond_signal(&cond);
printf("Producer: (too late) signaled\n");
return NULL;
}
int main(void)
{
pthread_t prod, cons;
pthread_create(&cons, NULL, consumer_with_timeout, NULL);
pthread_create(&prod, NULL, slow_producer, NULL);
pthread_join(cons, NULL);
pthread_join(prod, NULL);
return 0;
}
/*
* Output:
* Consumer: timed out waiting for data!
* Producer: (too late) signaled
*/
Interview Questions — Signal, Broadcast and Wait
ETIMEDOUT. When it returns ETIMEDOUT, the mutex is still re-locked (the function always re-locks before returning, even on timeout).