C++ condition_variable | Condition Variable Complete Guide

C++ condition_variable | Condition Variable Complete Guide

이 글의 핵심

C++ condition_variable synchronization tool for inter-thread event notification. Implement producer-consumer pattern, work queue, and barrier with wait, notify_one, notify_all, wait_for.

Introduction

C++‘s condition_variable is a synchronization tool for inter-thread event notification. Used to implement producer-consumer pattern, work queue, barrier, etc. To use an analogy, condition_variable is like waiting with a number ticket in a waiting room. When your number is called (notify), you wake up (wait ends) and start working.

After Reading This

  • Understand concept and usage of condition_variable
  • Grasp differences between wait, notify_one, notify_all
  • Implement producer-consumer pattern and work queue
  • Prevent Spurious Wakeup and Lost Wakeup

Reality in Production

When learning development, everything is clean and theoretical. But production is different. You wrestle with legacy code, chase tight deadlines, and face unexpected bugs. The content covered in this guide was initially learned as theory, but I realized “ah, that’s why it’s designed this way” while applying it to actual projects. What stands out in my memory is the trial and error from my first project. I did it as I learned from books but spent days not knowing why it didn’t work. Eventually, I found the problem through a senior developer’s code review and learned a lot in the process. This guide covers not only theory but also pitfalls you may encounter in practice and their solutions.

Table of Contents

  1. condition_variable Basics
  2. Practical Implementation
  3. Advanced Usage
  4. Performance Comparison
  5. Practical Cases
  6. Troubleshooting
  7. Conclusion

condition_variable Basics

Basic Concept

condition_variable waits for a thread until condition is met, and wakes it when condition is met. Here is detailed implementation code using C++. Import the necessary modules and process data with loops. Understand the role of each part while examining the code.

#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
std::condition_variable cv;
std::mutex mtx;
bool ready = false;
void worker() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return ready; });  // Wait until ready is true
    
    std::cout << "Start work" << std::endl;
}
void mainThread() {
    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true;
    }
    cv.notify_one();  // Wake waiting thread
}
int main() {
    std::thread t(worker);
    
    std::this_thread::sleep_for(std::chrono::seconds(1));
    mainThread();
    
    t.join();
    
    return 0;
}

Practical Implementation

1) Producer-Consumer Pattern

Here is detailed implementation code using C++. Import the necessary modules and process data with loops. Understand the role of each part while examining the code.

#include <condition_variable>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
std::queue<int> q;
std::mutex mtx;
std::condition_variable cv;
void producer() {
    for (int i = 0; i < 10; ++i) {
        {
            std::lock_guard<std::mutex> lock(mtx);
            q.push(i);
            std::cout << "Produce: " << i << std::endl;
        }
        cv.notify_one();
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}
void consumer() {
    for (int i = 0; i < 10; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, []{ return !q.empty(); });
        
        int value = q.front();
        q.pop();
        lock.unlock();
        
        std::cout << "Consume: " << value << std::endl;
    }
}
int main() {
    std::thread t1(producer);
    std::thread t2(consumer);
    
    t1.join();
    t2.join();
    
    return 0;
}

2) wait_for: Timeout

Here is detailed implementation code using C++. Import the necessary modules, process data with loops, and perform branching with conditionals. Understand the role of each part while examining the code.

#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
std::condition_variable cv;
std::mutex mtx;
bool ready = false;
void worker() {
    std::unique_lock<std::mutex> lock(mtx);
    
    if (cv.wait_for(lock, std::chrono::seconds(1), []{ return ready; })) {
        std::cout << "Condition met" << std::endl;
    } else {
        std::cout << "Timeout" << std::endl;
    }
}
int main() {
    std::thread t(worker);
    
    // Notify after 2 seconds (timeout)
    std::this_thread::sleep_for(std::chrono::seconds(2));
    
    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true;
    }
    cv.notify_one();
    
    t.join();
    
    return 0;
}

3) notify_one vs notify_all

Here is detailed implementation code using C++. Import the necessary modules and process data with loops. Understand the role of each part while examining the code.

#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
std::condition_variable cv;
std::mutex mtx;
bool ready = false;
void worker(int id) {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return ready; });
    
    std::cout << "Thread " << id << " woke up" << std::endl;
}
int main() {
    std::vector<std::thread> threads;
    for (int i = 0; i < 5; ++i) {
        threads.emplace_back(worker, i);
    }
    
    std::this_thread::sleep_for(std::chrono::seconds(1));
    
    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true;
    }
    
    // notify_one: Wake one only
    // cv.notify_one();
    
    // notify_all: Wake all
    cv.notify_all();
    
    for (auto& t : threads) {
        t.join();
    }
    
    return 0;
}

Summary

Key Points

  1. condition_variable: Tool for inter-thread event notification
  2. wait: Wait until condition is met
  3. notify_one: Wake one waiting thread
  4. notify_all: Wake all waiting threads
  5. unique_lock: Required for wait (can unlock/lock)

When to Use

Use condition_variable when:

  • Producer-consumer pattern
  • Work queue
  • Event notification
  • Thread synchronization ❌ Don’t use when:
  • Simple flag checking (use atomic)
  • No waiting needed (use mutex only)
  • Performance critical tight loops

Best Practices

  • ✅ Always check condition in wait predicate
  • ✅ Use unique_lock for wait
  • ✅ Minimize critical section
  • ✅ Handle Spurious Wakeup
  • ❌ Don’t forget to notify
  • ❌ Don’t hold lock while notifying (can, but less efficient)