Mastering C++ Condition Variables: From Basics to Producer-Consumer Patterns
This article explains the concept, core features, basic usage, member functions, best practices, and real-world scenarios of C++11's std::condition_variable, including a complete producer‑consumer example and comparisons with atomic operations.
In multithreaded programming, a condition variable is a powerful synchronization primitive that allows threads to be awakened only when a specific condition becomes true, avoiding busy‑waiting. C++11 introduced the standard std::condition_variable class.
1. Basic concept of condition variables
Condition variables are synchronization primitives used with a mutex to implement waiting and notification between threads. They solve the problem of a thread needing to wait for a condition without wasting CPU cycles; the thread sleeps and is awakened only when the condition may have changed.
Core characteristics:
Threads can block until a condition becomes true.
When the condition changes, waiting threads are notified.
Must be used together with a mutex to guarantee atomic condition checks.
2. Basic usage of std::condition_variable
Header inclusion
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>Basic structure
std::mutex mtx;
std::condition_variable cv;
bool ready = false; // condition variable
// waiting thread
void wait_thread() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; });
std::cout << "Thread awakened, condition satisfied" << std::endl;
}
// notifying thread
void notify_thread() {
std::this_thread::sleep_for(std::chrono::seconds(2));
{
std::lock_guard<std::mutex> lock(mtx);
ready = true;
}
cv.notify_one(); // notify one waiting thread
}3. Producer‑consumer example
The most typical use case for condition variables is the producer‑consumer pattern.
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <chrono>
std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;
const int MAX_SIZE = 5;
void producer(int id) {
for (int i = 0; i < 10; ++i) {
std::unique_lock<std::mutex> lock(mtx);
// wait until queue is not full
cv.wait(lock, []{ return data_queue.size() < MAX_SIZE; });
data_queue.push(i);
std::cout << "Producer " << id << " produced: " << i << std::endl;
lock.unlock();
cv.notify_all(); // notify consumers
}
}
void consumer(int id) {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
// wait until queue is not empty
cv.wait(lock, []{ return !data_queue.empty(); });
int data = data_queue.front();
data_queue.pop();
std::cout << "Consumer " << id << " consumed: " << data << std::endl;
lock.unlock();
cv.notify_all(); // notify producers
if (data == 9) break; // termination condition
}
}
int main() {
std::thread p1(producer, 1);
std::thread c1(consumer, 1);
std::thread c2(consumer, 2);
p1.join();
c1.join();
c2.join();
return 0;
}4. Member functions of condition_variable
wait()
// unconditional wait
void wait(std::unique_lock<std::mutex>& lock);
// wait with predicate (recommended)
void wait(std::unique_lock<std::mutex>& lock, Predicate pred);notify functions
// notify one waiting thread
void notify_one() noexcept;
// notify all waiting threads
void notify_all() noexcept;Timed wait functions
// wait for a duration
std::cv_status wait_for(std::unique_lock<std::mutex>& lock,
const std::chrono::duration<Rep,Period>& rel_time);
// wait for a duration with predicate
bool wait_for(std::unique_lock<std::mutex>& lock,
const std::chrono::duration<Rep,Period>& rel_time,
Predicate pred);5. Best practices and pitfalls
Spurious wakeups
// wrong: may suffer from spurious wakeups
cv.wait(lock);
// correct: use a predicate
cv.wait(lock, []{ return ready; });Mutex usage
Use std::unique_lock instead of std::lock_guard because wait() needs to unlock and relock the mutex.
Resource management
{
std::lock_guard<std::mutex> lock(mtx);
ready = true; // modify condition while holding the lock
}
cv.notify_one(); // send notification6. Comparison with atomic operations
Condition variables are suited for complex synchronization conditions and do not consume CPU while waiting, whereas atomic operations are simple flag checks that may busy‑wait.
7. Real‑world scenarios
Task queues: scheduling work in a thread pool.
Resource pools: database or memory connection pools.
Event handling: event loops in GUI applications.
State synchronization: multiple threads waiting for a shared state.
Condition variables are an indispensable tool for efficient inter‑thread communication in C++.
Key takeaways
Always pair a condition variable with a mutex.
Use a predicate to guard against spurious wakeups.
Hold the lock while modifying the condition and release it after notifying.
Choose notify_one() or notify_all() according to the scenario.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
php Courses
php中文网's platform for the latest courses and technical articles, helping PHP learners advance quickly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
