You’re asking for a clear, beginner-friendly English explanation of Condition Variable—a critical synchronization tool for multi-threading that enables threads to wait for a specific condition to be true (complementing mutexes/semaphores we covered earlier). I’ll break down its definition, core purpose, how it works, and provide a practical code example to illustrate its use.
1. Core Definition & Simple Explanation
- Term: Condition Variable (Pronunciation: /kənˈdɪʃn ˈveəriəbl/)
- Simple Definition:A condition variable is a synchronization primitive that allows threads to wait (block) until a specific condition is met (e.g., “a queue has data to process” or “a resource is available”). It works hand-in-hand with a mutex to ensure thread safety, and can wake up one or all waiting threads when the condition is satisfied.
- Analogy for Beginners:Imagine a café (process) where baristas (threads) wait for customer orders (the “condition”):
- Baristas lock a mutex (to avoid chaos) and check if there are orders. If no orders (condition false), they wait on a condition variable (go idle, release the mutex so others can act).
- When a customer places an order (condition becomes true), the cashier signals the condition variable—waking a barista to process the order (the barista re-acquires the mutex and checks the condition again).
2. Core Concepts & Operations
Condition variables always require a mutex (to protect the shared state that defines the condition). Here are the key operations:
| Operation | Explanation |
|---|---|
pthread_cond_wait() | 1. Releases the associated mutex (so other threads can modify the condition).2. Blocks the thread until another thread signals the condition variable.3. Re-acquires the mutex before returning (critical for thread safety). |
pthread_cond_signal() | Wakes up one waiting thread (if any) to check the condition. |
pthread_cond_broadcast() | Wakes up all waiting threads (useful if multiple threads need to react to the condition). |
Critical Rule for Beginners
Always check the condition in a loop (not just once) after waking up—this avoids “spurious wakeups” (threads waking up even if the condition is still false, a rare but possible OS behavior).
3. Practical Code Example (Producer-Consumer Problem)
The producer-consumer problem is the classic use case for condition variables:
- Producer Thread: Adds items to a shared queue (condition: “queue is not full”).
- Consumer Thread: Removes items from the queue (condition: “queue is not empty”).
- Condition variables ensure consumers wait if the queue is empty, and producers wait if the queue is full.
c
运行
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
// Shared queue configuration
#define QUEUE_SIZE 5
int queue[QUEUE_SIZE];
int queue_count = 0; // Tracks number of items in the queue (shared state)
// Synchronization primitives: mutex + condition variables
pthread_mutex_t queue_mutex;
pthread_cond_t cond_queue_not_empty; // For consumers (wait if queue is empty)
pthread_cond_t cond_queue_not_full; // For producers (wait if queue is full)
// Producer thread: adds items to the queue
void* producer(void* arg) {
long producer_id = (long)arg;
int item = 0;
while (item < 10) { // Produce 10 items total
// Step 1: Lock mutex to protect shared queue state
pthread_mutex_lock(&queue_mutex);
// Step 2: Wait if queue is full (loop to handle spurious wakeups)
while (queue_count == QUEUE_SIZE) {
printf("Producer %ld: Queue full, waiting...\n", producer_id);
pthread_cond_wait(&cond_queue_not_full, &queue_mutex);
}
// Step 3: Add item to queue (condition is true: queue not full)
queue[queue_count++] = item;
printf("Producer %ld: Added item %d (queue count: %d)\n", producer_id, item, queue_count);
item++;
// Step 4: Signal consumers (queue is now not empty)
pthread_cond_signal(&cond_queue_not_empty);
// Step 5: Unlock mutex
pthread_mutex_unlock(&queue_mutex);
// Simulate production time
sleep(1);
}
pthread_exit(NULL);
}
// Consumer thread: removes items from the queue
void* consumer(void* arg) {
long consumer_id = (long)arg;
while (1) { // Run indefinitely (we'll stop it after all items are processed)
// Step 1: Lock mutex to protect shared queue state
pthread_mutex_lock(&queue_mutex);
// Step 2: Wait if queue is empty (loop to handle spurious wakeups)
while (queue_count == 0) {
printf("Consumer %ld: Queue empty, waiting...\n", consumer_id);
pthread_cond_wait(&cond_queue_not_empty, &queue_mutex);
}
// Step 3: Remove item from queue (condition is true: queue not empty)
int item = queue[--queue_count];
printf("Consumer %ld: Removed item %d (queue count: %d)\n", consumer_id, item, queue_count);
// Step 4: Signal producers (queue is now not full)
pthread_cond_signal(&cond_queue_not_full);
// Step 5: Unlock mutex
pthread_mutex_unlock(&queue_mutex);
// Exit if all items (0-9) are processed
if (item == 9) break;
// Simulate consumption time
sleep(2);
}
pthread_exit(NULL);
}
int main() {
pthread_t prod_thread, cons_thread;
// Initialize mutex and condition variables
pthread_mutex_init(&queue_mutex, NULL);
pthread_cond_init(&cond_queue_not_empty, NULL);
pthread_cond_init(&cond_queue_not_full, NULL);
// Create producer and consumer threads
pthread_create(&prod_thread, NULL, producer, (void*)1);
pthread_create(&cons_thread, NULL, consumer, (void*)1);
// Wait for threads to finish
pthread_join(prod_thread, NULL);
pthread_join(cons_thread, NULL);
// Cleanup
pthread_mutex_destroy(&queue_mutex);
pthread_cond_destroy(&cond_queue_not_empty);
pthread_cond_destroy(&cond_queue_not_full);
printf("\nAll items processed. Program completed.\n");
return 0;
}
Compile & Run (Linux/macOS):
bash
运行
gcc condition_var_example.c -o condition_var_example -pthread
./condition_var_example
Expected Output (Coordinated Production/Consumption):
plaintext
Producer 1: Added item 0 (queue count: 1)
Consumer 1: Removed item 0 (queue count: 0)
Consumer 1: Queue empty, waiting...
Producer 1: Added item 1 (queue count: 1)
Producer 1: Added item 2 (queue count: 2)
Consumer 1: Removed item 1 (queue count: 1)
Producer 1: Added item 3 (queue count: 2)
Producer 1: Added item 4 (queue count: 3)
Consumer 1: Removed item 2 (queue count: 2)
Producer 1: Added item 5 (queue count: 3)
Producer 1: Added item 6 (queue count: 4)
Consumer 1: Removed item 3 (queue count: 3)
Producer 1: Added item 7 (queue count: 4)
Producer 1: Added item 8 (queue count: 5)
Producer 1: Queue full, waiting...
Consumer 1: Removed item 4 (queue count: 4)
Producer 1: Added item 9 (queue count: 5)
Producer 1: Queue full, waiting...
Consumer 1: Removed item 5 (queue count: 4)
Consumer 1: Removed item 6 (queue count: 3)
Consumer 1: Removed item 7 (queue count: 2)
Consumer 1: Removed item 8 (queue count: 1)
Consumer 1: Removed item 9 (queue count: 0)
All items processed. Program completed.
4. Key Use Cases for Condition Variables
- Producer-Consumer Problems: Coordinate threads adding/removing items from a queue (the most common use case).
- Wait-for-Event Scenarios: Threads wait for an event (e.g., “data received from network”, “file write completed”) instead of polling (checking repeatedly, which wastes CPU).
- Thread Coordination: Synchronize multiple threads to run in a specific order (e.g., wait for all worker threads to finish before a main thread proceeds).
5. Condition Variable vs. Semaphore/Mutex
| Tool | Primary Purpose | Key Difference |
|---|---|---|
| Mutex | Mutual exclusion (one thread at a time) | No waiting for conditions—only locks resources. |
| Semaphore | Limit concurrent access to N resources | Counts resources, not tied to a logical condition. |
| Condition Variable | Wait for a logical condition to be true | Requires a mutex; wakes threads only when the condition changes (avoids polling). |
Summary
It’s the ideal tool for the producer-consumer problem and other scenarios where threads need to wait for events (not just resource availability).
A Condition Variable lets threads wait for a specific logical condition (e.g., “queue not empty”) and is always used with a mutex for thread safety.
Core operations: pthread_cond_wait() (wait for condition), pthread_cond_signal() (wake one thread), pthread_cond_broadcast() (wake all threads).
Always check the condition in a loop after waking up to handle spurious wakeups—this is a critical best practice for beginners.
- High-Performance Waterproof Solar Connectors
- Durable IP68 Waterproof Solar Connectors for Outdoor Use
- High-Quality Tinned Copper Material for Durability
- High-Quality Tinned Copper Material for Long Service Life
- Y Branch Parallel Solar Connector for Enhanced Power
- 10AWG Tinned Copper Solar Battery Cables
- NEMA 5-15P to Powercon Extension Cable Overview
- Dual Port USB 3.0 Adapter for Optimal Speed
- 4-Pin XLR Connector: Reliable Audio Transmission
- 4mm Banana to 2mm Pin Connector: Your Audio Solution
- 12GB/s Mini SAS to U.2 NVMe Cable for Fast Data Transfer
- CAB-STK-E Stacking Cable: 40Gbps Performance
- High-Performance CAB-STK-E Stacking Cable Explained
- Best 10M OS2 LC to LC Fiber Patch Cable for Data Centers
- Mini SAS HD Cable: Boost Data Transfer at 12 Gbps
- Multi Rate SFP+: Enhance Your Network Speed
- Best 6.35mm to MIDI Din Cable for Clear Sound
- 15 Pin SATA Power Splitter: Solutions for Your Device Needs
- 9-Pin S-Video Cable: Enhance Your Viewing Experience
- USB 9-Pin to Standard USB 2.0 Adapter: Easy Connection
- 3 Pin to 4 Pin Fan Adapter: Optimize Your PC Cooling
- S-Video to RCA Cable: High-Definition Connections Made Easy
- 6.35mm TS Extension Cable: High-Quality Sound Solution
- BlackBerry Curve 9360: Key Features and Specs






















Leave a comment