Loading learning content...
Having explored each pthread condition variable function individually, this page synthesizes everything into production-ready patterns that represent industry best practices.
Condition variables are deceptively simple—four functions, straightforward semantics. Yet they are among the most error-prone synchronization primitives. The difference between correct and subtly broken code often lies in adherence to established patterns.
This page presents:
Master these patterns, and you will write condition variable code that is correct by construction.
By completing this page, you will: (1) Internalize the canonical while-loop wait pattern, (2) Implement producer-consumer, readers-writers, and thread pool patterns correctly, (3) Apply defensive techniques for error handling and shutdown, (4) Recognize and avoid common anti-patterns, (5) Know strategies for testing condition variable code, and (6) Achieve production-level proficiency in pthread condition variable usage.
Every correct use of condition variables follows the same fundamental template. This template handles spurious wakeups, lost signals, and all edge cases.
123456789101112131415161718192021222324252627282930313233343536373839404142
#include <pthread.h>#include <stdbool.h> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // Your shared state (the "condition" being waited on)bool condition_predicate = false; // Replace with your actual condition /* * ======================================================== * THE CANONICAL CONDITION VARIABLE PATTERN * ======================================================== * * This pattern is MANDATORY for correctness. Deviation leads to bugs. * Memorize it. Apply it without exception. */ // WAITER: Thread that waits for condition to become truevoid wait_for_condition(void) { pthread_mutex_lock(&mutex); // 1. Always lock first while (!condition_predicate) { // 2. WHILE, never IF pthread_cond_wait(&cond, &mutex); // 3. Wait releases mutex atomically } // 4. Loop rechecks after wakeup // 5. Condition is true AND mutex is held here // ... do work that requires condition to be true ... pthread_mutex_unlock(&mutex); // 6. Release mutex when done} // SIGNALER: Thread that makes condition true and notifies waitersvoid signal_condition(void) { pthread_mutex_lock(&mutex); // 1. Lock before modifying state condition_predicate = true; // 2. Modify shared state pthread_cond_signal(&cond); // 3. Signal (or broadcast) UNDER LOCK pthread_mutex_unlock(&mutex); // 4. Release mutex}The while loop handles multiple scenarios:
1. Spurious Wakeups
POSIX explicitly permits pthread_cond_wait() to return without a signal. After return, the condition might still be false. The while loop rechecks.
2. Stolen Signals
With multiple waiters, a signal intended for thread A might wake thread B. Thread B checks the condition, finds it false (thread A consumed the resource), and the while loop sends B back to sleep.
3. Broadcast Wake
After broadcast, all threads wake. Only some may find the condition true. Others recheck via the while loop and go back to sleep.
4. Condition Becomes False Again
Another thread might change the condition between the signal and the waiter reacquiring the mutex. The while loop catches this.
This pattern is universal across languages and synchronization APIs:
| Language | Lock | Wait | Signal |
|---|---|---|---|
| C (pthreads) | pthread_mutex_lock() | pthread_cond_wait() | pthread_cond_signal() |
| Java | synchronized / lock() | wait() | notify() |
| Python | lock.acquire() | condition.wait() | condition.notify() |
| C++ | std::unique_lock | cond.wait(lock) | cond.notify_one() |
| Go | mu.Lock() | cond.Wait() | cond.Signal() |
| Rust | let guard = mutex.lock() | cond.wait(guard) | cond.notify_one() |
Once you internalize this pattern for pthreads, it transfers directly to every other language and runtime. The while-loop-with-recheck is the universal correct approach to condition variables everywhere.
The producer-consumer pattern is the most common use case for condition variables. It coordinates threads that produce work with threads that consume it, with a bounded buffer in between.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
#include <pthread.h>#include <stdlib.h>#include <stdbool.h>#include <stdio.h>#include <errno.h> // =====================================================// Production Bounded Buffer with Full Error Handling// ===================================================== typedef struct { pthread_mutex_t mutex; pthread_cond_t not_empty; // Consumers wait when buffer empty pthread_cond_t not_full; // Producers wait when buffer full void **items; // Buffer storage int capacity; // Maximum items int count; // Current item count int head; // Read position int tail; // Write position bool shutdown; // Graceful shutdown flag int producers_active; // Active producer count int consumers_waiting; // Consumers currently waiting} buffer_t; // =====================================================// Initialization// ===================================================== int buffer_init(buffer_t *buf, int capacity) { if (capacity <= 0) return EINVAL; buf->items = malloc(sizeof(void *) * capacity); if (!buf->items) return ENOMEM; int rc = pthread_mutex_init(&buf->mutex, NULL); if (rc != 0) { free(buf->items); return rc; } // Use CLOCK_MONOTONIC for robust timeouts pthread_condattr_t attr; pthread_condattr_init(&attr); pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); rc = pthread_cond_init(&buf->not_empty, &attr); if (rc != 0) { pthread_mutex_destroy(&buf->mutex); free(buf->items); pthread_condattr_destroy(&attr); return rc; } rc = pthread_cond_init(&buf->not_full, &attr); if (rc != 0) { pthread_cond_destroy(&buf->not_empty); pthread_mutex_destroy(&buf->mutex); free(buf->items); pthread_condattr_destroy(&attr); return rc; } pthread_condattr_destroy(&attr); buf->capacity = capacity; buf->count = 0; buf->head = 0; buf->tail = 0; buf->shutdown = false; buf->producers_active = 0; buf->consumers_waiting = 0; return 0;} // =====================================================// Producer: Put Item with Timeout// ===================================================== typedef enum { BUF_OK = 0, BUF_TIMEOUT, BUF_SHUTDOWN, BUF_ERROR} buffer_status_t; buffer_status_t buffer_put(buffer_t *buf, void *item, int timeout_ms) { struct timespec deadline; clock_gettime(CLOCK_MONOTONIC, &deadline); deadline.tv_sec += timeout_ms / 1000; deadline.tv_nsec += (timeout_ms % 1000) * 1000000L; if (deadline.tv_nsec >= 1000000000L) { deadline.tv_sec++; deadline.tv_nsec -= 1000000000L; } pthread_mutex_lock(&buf->mutex); buf->producers_active++; // Wait for space while (buf->count == buf->capacity && !buf->shutdown) { int rc = pthread_cond_timedwait(&buf->not_full, &buf->mutex, &deadline); if (rc == ETIMEDOUT) { buf->producers_active--; pthread_mutex_unlock(&buf->mutex); return BUF_TIMEOUT; } } if (buf->shutdown) { buf->producers_active--; pthread_mutex_unlock(&buf->mutex); return BUF_SHUTDOWN; } // Add item buf->items[buf->tail] = item; buf->tail = (buf->tail + 1) % buf->capacity; buf->count++; buf->producers_active--; // Signal consumer pthread_cond_signal(&buf->not_empty); pthread_mutex_unlock(&buf->mutex); return BUF_OK;} // =====================================================// Consumer: Get Item with Timeout// ===================================================== buffer_status_t buffer_get(buffer_t *buf, void **item, int timeout_ms) { struct timespec deadline; clock_gettime(CLOCK_MONOTONIC, &deadline); deadline.tv_sec += timeout_ms / 1000; deadline.tv_nsec += (timeout_ms % 1000) * 1000000L; if (deadline.tv_nsec >= 1000000000L) { deadline.tv_sec++; deadline.tv_nsec -= 1000000000L; } pthread_mutex_lock(&buf->mutex); buf->consumers_waiting++; // Wait for item while (buf->count == 0 && !buf->shutdown) { int rc = pthread_cond_timedwait(&buf->not_empty, &buf->mutex, &deadline); if (rc == ETIMEDOUT) { buf->consumers_waiting--; pthread_mutex_unlock(&buf->mutex); return BUF_TIMEOUT; } } buf->consumers_waiting--; // Check for shutdown with empty buffer if (buf->count == 0 && buf->shutdown) { pthread_mutex_unlock(&buf->mutex); return BUF_SHUTDOWN; } // Remove item *item = buf->items[buf->head]; buf->head = (buf->head + 1) % buf->capacity; buf->count--; // Signal producer pthread_cond_signal(&buf->not_full); pthread_mutex_unlock(&buf->mutex); return BUF_OK;} // =====================================================// Graceful Shutdown// ===================================================== void buffer_shutdown(buffer_t *buf) { pthread_mutex_lock(&buf->mutex); buf->shutdown = true; // Wake all waiters pthread_cond_broadcast(&buf->not_empty); pthread_cond_broadcast(&buf->not_full); pthread_mutex_unlock(&buf->mutex);}Thread pools are a fundamental building block for concurrent servers and applications. They maintain a set of worker threads that process tasks from a shared queue.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
#include <pthread.h>#include <stdlib.h>#include <stdbool.h>#include <stdio.h> // =====================================================// Production Thread Pool with Condition Variables// ===================================================== typedef void (*task_func_t)(void *arg); typedef struct task_node { task_func_t func; void *arg; struct task_node *next;} task_node_t; typedef struct { pthread_mutex_t mutex; pthread_cond_t task_available; // Workers wait for tasks pthread_cond_t task_complete; // Wait-for-completion support task_node_t *head; // Task queue head task_node_t *tail; // Task queue tail int pending_tasks; // Tasks in queue int active_tasks; // Tasks being processed pthread_t *threads; // Worker thread handles int thread_count; bool shutdown;} thread_pool_t; // =====================================================// Worker Thread// ===================================================== static void *worker_thread(void *arg) { thread_pool_t *pool = (thread_pool_t *)arg; while (true) { pthread_mutex_lock(&pool->mutex); // Wait for task or shutdown while (pool->pending_tasks == 0 && !pool->shutdown) { pthread_cond_wait(&pool->task_available, &pool->mutex); } // Check for shutdown with empty queue if (pool->shutdown && pool->pending_tasks == 0) { pthread_mutex_unlock(&pool->mutex); break; } // Dequeue task task_node_t *task = pool->head; pool->head = task->next; if (!pool->head) pool->tail = NULL; pool->pending_tasks--; pool->active_tasks++; pthread_mutex_unlock(&pool->mutex); // Execute task (outside lock!) task->func(task->arg); free(task); // Task complete pthread_mutex_lock(&pool->mutex); pool->active_tasks--; // Signal completion waiters if (pool->pending_tasks == 0 && pool->active_tasks == 0) { pthread_cond_broadcast(&pool->task_complete); } pthread_mutex_unlock(&pool->mutex); } return NULL;} // =====================================================// Pool Creation// ===================================================== thread_pool_t *pool_create(int num_threads) { thread_pool_t *pool = calloc(1, sizeof(thread_pool_t)); if (!pool) return NULL; pthread_mutex_init(&pool->mutex, NULL); pthread_cond_init(&pool->task_available, NULL); pthread_cond_init(&pool->task_complete, NULL); pool->threads = malloc(sizeof(pthread_t) * num_threads); pool->thread_count = num_threads; for (int i = 0; i < num_threads; i++) { pthread_create(&pool->threads[i], NULL, worker_thread, pool); } return pool;} // =====================================================// Submit Task// ===================================================== bool pool_submit(thread_pool_t *pool, task_func_t func, void *arg) { task_node_t *task = malloc(sizeof(task_node_t)); if (!task) return false; task->func = func; task->arg = arg; task->next = NULL; pthread_mutex_lock(&pool->mutex); if (pool->shutdown) { pthread_mutex_unlock(&pool->mutex); free(task); return false; } // Enqueue if (pool->tail) { pool->tail->next = task; } else { pool->head = task; } pool->tail = task; pool->pending_tasks++; // Signal ONE worker pthread_cond_signal(&pool->task_available); pthread_mutex_unlock(&pool->mutex); return true;} // =====================================================// Wait for All Tasks to Complete// ===================================================== void pool_wait(thread_pool_t *pool) { pthread_mutex_lock(&pool->mutex); while (pool->pending_tasks > 0 || pool->active_tasks > 0) { pthread_cond_wait(&pool->task_complete, &pool->mutex); } pthread_mutex_unlock(&pool->mutex);} // =====================================================// Graceful Shutdown// ===================================================== void pool_destroy(thread_pool_t *pool) { pthread_mutex_lock(&pool->mutex); pool->shutdown = true; pthread_cond_broadcast(&pool->task_available); pthread_mutex_unlock(&pool->mutex); // Join all workers for (int i = 0; i < pool->thread_count; i++) { pthread_join(pool->threads[i], NULL); } // Cleanup pthread_cond_destroy(&pool->task_complete); pthread_cond_destroy(&pool->task_available); pthread_mutex_destroy(&pool->mutex); free(pool->threads); free(pool);}Workers unlock the mutex BEFORE executing the task, allowing other workers to dequeue concurrently. The task_complete condition enables waiting for all work to finish. Signal (not broadcast) is used when submitting since each task needs exactly one worker.
The readers-writers problem allows multiple simultaneous readers but exclusive writer access. Condition variables coordinate between these modes.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
#include <pthread.h> // =====================================================// Readers-Writers Lock (Writers Preference)// ===================================================== typedef struct { pthread_mutex_t mutex; pthread_cond_t readers_ok; // Readers wait here pthread_cond_t writers_ok; // Writers wait here int active_readers; // Currently reading int waiting_readers; // Want to read int active_writers; // Currently writing (0 or 1) int waiting_writers; // Want to write} rwlock_t; void rwlock_init(rwlock_t *rw) { pthread_mutex_init(&rw->mutex, NULL); pthread_cond_init(&rw->readers_ok, NULL); pthread_cond_init(&rw->writers_ok, NULL); rw->active_readers = 0; rw->waiting_readers = 0; rw->active_writers = 0; rw->waiting_writers = 0;} // =====================================================// Reader Lock: Multiple readers allowed// ===================================================== void rwlock_read_lock(rwlock_t *rw) { pthread_mutex_lock(&rw->mutex); rw->waiting_readers++; // Wait if writer is active OR writers are waiting (preference) while (rw->active_writers > 0 || rw->waiting_writers > 0) { pthread_cond_wait(&rw->readers_ok, &rw->mutex); } rw->waiting_readers--; rw->active_readers++; pthread_mutex_unlock(&rw->mutex);} void rwlock_read_unlock(rwlock_t *rw) { pthread_mutex_lock(&rw->mutex); rw->active_readers--; // If last reader and writers waiting, signal ONE writer if (rw->active_readers == 0 && rw->waiting_writers > 0) { pthread_cond_signal(&rw->writers_ok); } pthread_mutex_unlock(&rw->mutex);} // =====================================================// Writer Lock: Exclusive access// ===================================================== void rwlock_write_lock(rwlock_t *rw) { pthread_mutex_lock(&rw->mutex); rw->waiting_writers++; // Wait for no active readers or writers while (rw->active_readers > 0 || rw->active_writers > 0) { pthread_cond_wait(&rw->writers_ok, &rw->mutex); } rw->waiting_writers--; rw->active_writers = 1; pthread_mutex_unlock(&rw->mutex);} void rwlock_write_unlock(rwlock_t *rw) { pthread_mutex_lock(&rw->mutex); rw->active_writers = 0; // Preference: wake writers first, then readers if (rw->waiting_writers > 0) { pthread_cond_signal(&rw->writers_ok); } else if (rw->waiting_readers > 0) { // Wake ALL waiting readers pthread_cond_broadcast(&rw->readers_ok); } pthread_mutex_unlock(&rw->mutex);}Production code must handle cancellation, timeouts, signal delivery, and cleanup robustly. These patterns provide that defense.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
#include <pthread.h>#include <stdbool.h>#include <errno.h> // =====================================================// Cancellation-Safe Wait with Cleanup Handler// ===================================================== typedef struct { pthread_mutex_t mutex; pthread_cond_t cond; bool ready;} sync_point_t; static void cleanup_mutex(void *arg) { pthread_mutex_unlock((pthread_mutex_t *)arg);} void sync_wait_cancellable(sync_point_t *sp) { pthread_mutex_lock(&sp->mutex); // Register cleanup handler BEFORE wait pthread_cleanup_push(cleanup_mutex, &sp->mutex); while (!sp->ready) { // If cancelled while waiting, cleanup handler runs pthread_cond_wait(&sp->cond, &sp->mutex); } // Pop cleanup; 0 = don't execute (we'll unlock normally) pthread_cleanup_pop(0); pthread_mutex_unlock(&sp->mutex);} // =====================================================// Robust Timeout with Retry// ===================================================== typedef enum { WAIT_SUCCESS, WAIT_TIMEOUT, WAIT_INTERRUPTED, WAIT_SHUTDOWN} wait_result_t; wait_result_t robust_timed_wait(sync_point_t *sp, int timeout_ms) { struct timespec deadline; clock_gettime(CLOCK_MONOTONIC, &deadline); deadline.tv_sec += timeout_ms / 1000; deadline.tv_nsec += (timeout_ms % 1000) * 1000000L; if (deadline.tv_nsec >= 1000000000L) { deadline.tv_sec++; deadline.tv_nsec -= 1000000000L; } pthread_mutex_lock(&sp->mutex); while (!sp->ready) { int rc = pthread_cond_timedwait(&sp->cond, &sp->mutex, &deadline); if (rc == ETIMEDOUT) { pthread_mutex_unlock(&sp->mutex); return WAIT_TIMEOUT; } // Handle rare error cases if (rc != 0 && rc != EINTR) { pthread_mutex_unlock(&sp->mutex); return WAIT_INTERRUPTED; } // rc == 0 or EINTR: continue loop, recheck condition } pthread_mutex_unlock(&sp->mutex); return WAIT_SUCCESS;} // =====================================================// Double-Checked Initialization (Singleton)// ===================================================== typedef struct { pthread_mutex_t mutex; pthread_cond_t init_complete; void *resource; bool initializing; bool initialized;} lazy_resource_t; void *get_resource(lazy_resource_t *lr) { pthread_mutex_lock(&lr->mutex); if (lr->initialized) { void *result = lr->resource; pthread_mutex_unlock(&lr->mutex); return result; } if (lr->initializing) { // Another thread is initializing; wait while (lr->initializing) { pthread_cond_wait(&lr->init_complete, &lr->mutex); } void *result = lr->resource; pthread_mutex_unlock(&lr->mutex); return result; } // We will initialize lr->initializing = true; pthread_mutex_unlock(&lr->mutex); // Do expensive initialization OUTSIDE lock void *new_resource = expensive_initialization(); pthread_mutex_lock(&lr->mutex); lr->resource = new_resource; lr->initialized = true; lr->initializing = false; pthread_cond_broadcast(&lr->init_complete); pthread_mutex_unlock(&lr->mutex); return new_resource;}These are patterns that appear in production code but are fundamentally flawed. Recognizing and avoiding them is essential.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
#include <pthread.h>#include <stdbool.h>#include <unistd.h> // =====================================================// ANTI-PATTERN 1: If Instead of While// ===================================================== void *anti_if_instead_of_while(void *arg) { pthread_mutex_lock(&mutex); if (!condition) { // WRONG! pthread_cond_wait(&cond, &mutex); } // BUG: condition might be false after spurious wakeup process(); // May crash or corrupt data pthread_mutex_unlock(&mutex); return NULL;} // =====================================================// ANTI-PATTERN 2: Checking Condition Without Lock// ===================================================== void *anti_check_without_lock(void *arg) { // WRONG: Data race! Reading condition outside lock while (!condition) { pthread_mutex_lock(&mutex); pthread_cond_wait(&cond, &mutex); pthread_mutex_unlock(&mutex); } // Even if loop exits, condition could immediately become false process(); return NULL;} // CORRECT:void *correct_check_under_lock(void *arg) { pthread_mutex_lock(&mutex); while (!condition) { pthread_cond_wait(&cond, &mutex); } process(); // Lock still held, condition guaranteed pthread_mutex_unlock(&mutex); return NULL;} // =====================================================// ANTI-PATTERN 3: Different Mutexes with Same Cond Var// ===================================================== pthread_mutex_t mutex_a = PTHREAD_MUTEX_INITIALIZER;pthread_mutex_t mutex_b = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t shared_cond = PTHREAD_COND_INITIALIZER; void *anti_different_mutexes_a(void *arg) { pthread_mutex_lock(&mutex_a); pthread_cond_wait(&shared_cond, &mutex_a); // Uses mutex_a pthread_mutex_unlock(&mutex_a); return NULL;} void *anti_different_mutexes_b(void *arg) { pthread_mutex_lock(&mutex_b); pthread_cond_wait(&shared_cond, &mutex_b); // Uses mutex_b - UNDEFINED! pthread_mutex_unlock(&mutex_b); return NULL;} // =====================================================// ANTI-PATTERN 4: Busy Waiting in Loop// ===================================================== void *anti_busy_wait(void *arg) { while (!condition) { pthread_mutex_lock(&mutex); bool c = condition; pthread_mutex_unlock(&mutex); if (!c) { usleep(1000); // WRONG: Still burns CPU, adds latency } } return NULL;} // CORRECT: Use condition variablevoid *correct_blocking_wait(void *arg) { pthread_mutex_lock(&mutex); while (!condition) { pthread_cond_wait(&cond, &mutex); // Blocks, no CPU use } pthread_mutex_unlock(&mutex); return NULL;} // =====================================================// ANTI-PATTERN 5: Forgetting to Signal// ===================================================== void anti_no_signal(void) { pthread_mutex_lock(&mutex); condition = true; pthread_mutex_unlock(&mutex); // WHERE IS THE SIGNAL? Waiters wait forever!} // =====================================================// ANTI-PATTERN 6: Signal with No State Change// ===================================================== void anti_empty_signal(void) { pthread_mutex_lock(&mutex); // No state change! pthread_cond_signal(&cond); // Wakes thread that rechecks and sleeps pthread_mutex_unlock(&mutex); // Wasted wakeup, confusing code}| Anti-Pattern | Consequence | Correct Approach |
|---|---|---|
| if instead of while | Spurious wakeup causes incorrect execution | Always use while loop |
| Check without lock | Data race; undefined behavior | Check predicate under lock |
| Different mutexes | Undefined behavior | One mutex per condition variable |
| Busy waiting | CPU waste; latency | Use pthread_cond_wait() |
| Forgotten signal | Waiters block forever | Always signal after state change |
| Signal without change | Wasted wakeup | Signal only when predicate changes |
Condition variable bugs are notoriously hard to reproduce and debug. Systematic testing approaches improve confidence.
Run operations at high volume and concurrency to expose race conditions:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
#include <pthread.h>#include <stdio.h>#include <assert.h>#include <stdlib.h> // =====================================================// Stress Test: High Volume Concurrent Operations// ===================================================== #define NUM_PRODUCERS 10#define NUM_CONSUMERS 10#define ITEMS_PER_PRODUCER 10000 buffer_t test_buffer;int items_produced = 0;int items_consumed = 0;pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER; void *stress_producer(void *arg) { for (int i = 0; i < ITEMS_PER_PRODUCER; i++) { void *item = malloc(sizeof(int)); *(int *)item = rand(); buffer_status_t s = buffer_put(&test_buffer, item, 10000); assert(s == BUF_OK); pthread_mutex_lock(&counter_mutex); items_produced++; pthread_mutex_unlock(&counter_mutex); } return NULL;} void *stress_consumer(void *arg) { while (1) { void *item; buffer_status_t s = buffer_get(&test_buffer, &item, 100); if (s == BUF_TIMEOUT) continue; if (s == BUF_SHUTDOWN) break; free(item); pthread_mutex_lock(&counter_mutex); items_consumed++; pthread_mutex_unlock(&counter_mutex); } return NULL;} void run_stress_test(void) { buffer_init(&test_buffer, 100); pthread_t producers[NUM_PRODUCERS]; pthread_t consumers[NUM_CONSUMERS]; for (int i = 0; i < NUM_CONSUMERS; i++) { pthread_create(&consumers[i], NULL, stress_consumer, NULL); } for (int i = 0; i < NUM_PRODUCERS; i++) { pthread_create(&producers[i], NULL, stress_producer, NULL); } for (int i = 0; i < NUM_PRODUCERS; i++) { pthread_join(producers[i], NULL); } // Wait for consumers to drain while (items_consumed < items_produced) { usleep(1000); } buffer_shutdown(&test_buffer); for (int i = 0; i < NUM_CONSUMERS; i++) { pthread_join(consumers[i], NULL); } printf("Produced: %d, Consumed: %d\n", items_produced, items_consumed); assert(items_produced == items_consumed); printf("STRESS TEST PASSED\n");} // =====================================================// Instrumentation: Track Wait Statistics// ===================================================== typedef struct { int wait_count; int wakeup_count; int spurious_wakeup_count;} wait_stats_t; __thread wait_stats_t thread_stats; void instrumented_wait(pthread_cond_t *cond, pthread_mutex_t *mutex, bool *predicate) { thread_stats.wait_count++; while (!*predicate) { pthread_cond_wait(cond, mutex); thread_stats.wakeup_count++; if (!*predicate) { thread_stats.spurious_wakeup_count++; } }}Compile your tests with -fsanitize=thread and run frequently. ThreadSanitizer catches races, lock-order inversions, and many condition variable misuses automatically. It's your first line of defense against concurrency bugs.
This page has consolidated pthread condition variable usage into production-ready patterns. These patterns represent decades of collective experience in concurrent programming.
You have completed the comprehensive study of pthread condition variables. You understand pthread_cond_t, wait, signal, broadcast, and the patterns that combine them into correct, efficient concurrent programs. These skills form the foundation for building any concurrent system on POSIX platforms.