[2026] C++ condition_variable | 조건 변수 완벽 가이드

[2026] C++ condition_variable | 조건 변수 완벽 가이드

이 글의 핵심

C++ condition_variable 스레드 간 이벤트 통지를 위한 동기화 도구. wait·notify_one·notify_all·wait_for로 생산자-소비자 패턴, 작업 큐, 배리어를 구현합니다.

들어가며

C++의 condition_variable스레드 간 이벤트 통지를 위한 동기화 도구입니다. 생산자-소비자 패턴, 작업 큐, 배리어 등을 구현할 때 사용합니다. 비유로 말씀드리면, condition_variable대기실에서 번호표를 들고 기다리는 것에 가깝습니다. 번호가 불리면 (notify) 깨어나서 (wait 종료) 작업을 시작합니다.

이 글을 읽으면

  • condition_variable의 개념과 사용법을 이해합니다
  • wait, notify_one, notify_all의 차이를 파악합니다
  • 생산자-소비자 패턴과 작업 큐를 구현합니다
  • Spurious Wakeup과 Lost Wakeup을 방지합니다

실무에서 마주한 현실

개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.

목차

  1. condition_variable 기초
  2. 실전 구현
  3. 고급 활용
  4. 성능 비교
  5. 실무 사례
  6. 트러블슈팅
  7. 마무리

condition_variable 기초

기본 개념

condition_variable조건이 충족될 때까지 스레드를 대기시키고, 조건이 충족되면 깨웁니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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; });  // ready가 true일 때까지 대기
    
    std::cout << "작업 시작" << std::endl;
}
void mainThread() {
    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true;
    }
    cv.notify_one();  // 대기 중인 스레드 깨우기
}
int main() {
    std::thread t(worker);
    
    std::this_thread::sleep_for(std::chrono::seconds(1));
    mainThread();
    
    t.join();
    
    return 0;
}

실전 구현

1) 생산자-소비자 패턴

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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 << "생산: " << 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 << "소비: " << value << std::endl;
    }
}
int main() {
    std::thread t1(producer);
    std::thread t2(consumer);
    
    t1.join();
    t2.join();
    
    return 0;
}

2) wait_for: 타임아웃

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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 << "조건 충족" << std::endl;
    } else {
        std::cout << "타임아웃" << std::endl;
    }
}
int main() {
    std::thread t(worker);
    
    // 2초 후 notify (타임아웃)
    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

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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 << "스레드 " << id << " 깨어남" << 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: 하나만 깨움
    // cv.notify_one();
    
    // notify_all: 모두 깨움
    cv.notify_all();
    
    for (auto& t : threads) {
        t.join();
    }
    
    return 0;
}

고급 활용

1) 안전한 큐

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <condition_variable>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
template<typename T>
class SafeQueue {
private:
    std::queue<T> queue_;
    std::mutex mtx_;
    std::condition_variable cv_;
    bool done_ = false;
    
public:
    void push(T value) {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            queue_.push(std::move(value));
        }
        cv_.notify_one();
    }
    
    bool pop(T& value) {
        std::unique_lock<std::mutex> lock(mtx_);
        cv_.wait(lock, [this]{ return !queue_.empty() || done_; });
        
        if (queue_.empty()) {
            return false;  // 종료
        }
        
        value = std::move(queue_.front());
        queue_.pop();
        return true;
    }
    
    void finish() {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            done_ = true;
        }
        cv_.notify_all();
    }
};
int main() {
    SafeQueue<int> queue;
    
    std::thread producer([&queue]() {
        for (int i = 0; i < 10; ++i) {
            queue.push(i);
            std::cout << "생산: " << i << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
        queue.finish();
    });
    
    std::thread consumer([&queue]() {
        int value;
        while (queue.pop(value)) {
            std::cout << "소비: " << value << std::endl;
        }
    });
    
    producer.join();
    consumer.join();
    
    return 0;
}

2) 작업 큐

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <condition_variable>
#include <functional>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>
class TaskQueue {
private:
    std::queue<std::function<void()>> tasks_;
    std::mutex mtx_;
    std::condition_variable cv_;
    bool stop_ = false;
    
public:
    void addTask(std::function<void()> task) {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            tasks_.push(std::move(task));
        }
        cv_.notify_one();
    }
    
    void worker() {
        while (true) {
            std::function<void()> task;
            
            {
                std::unique_lock<std::mutex> lock(mtx_);
                cv_.wait(lock, [this]{ return !tasks_.empty() || stop_; });
                
                if (stop_ && tasks_.empty()) {
                    return;
                }
                
                task = std::move(tasks_.front());
                tasks_.pop();
            }
            
            task();
        }
    }
    
    void shutdown() {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            stop_ = true;
        }
        cv_.notify_all();
    }
};
int main() {
    TaskQueue queue;
    
    std::vector<std::thread> workers;
    for (int i = 0; i < 4; ++i) {
        workers.emplace_back(&TaskQueue::worker, &queue);
    }
    
    for (int i = 0; i < 10; ++i) {
        queue.addTask([i]() {
            std::cout << "작업 " << i << " 실행" << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        });
    }
    
    std::this_thread::sleep_for(std::chrono::seconds(2));
    queue.shutdown();
    
    for (auto& t : workers) {
        t.join();
    }
    
    return 0;
}

3) 배리어

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
class Barrier {
private:
    std::mutex mtx_;
    std::condition_variable cv_;
    int count_;
    int waiting_ = 0;
    
public:
    Barrier(int count) : count_(count) {}
    
    void wait() {
        std::unique_lock<std::mutex> lock(mtx_);
        ++waiting_;
        
        if (waiting_ == count_) {
            waiting_ = 0;
            cv_.notify_all();
        } else {
            cv_.wait(lock, [this]{ return waiting_ == 0; });
        }
    }
};
void worker(int id, Barrier& barrier) {
    std::cout << "스레드 " << id << " 작업 1" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(100 * id));
    
    barrier.wait();  // 모든 스레드 대기
    
    std::cout << "스레드 " << id << " 작업 2" << std::endl;
}
int main() {
    Barrier barrier(3);
    
    std::thread t1(worker, 1, std::ref(barrier));
    std::thread t2(worker, 2, std::ref(barrier));
    std::thread t3(worker, 3, std::ref(barrier));
    
    t1.join();
    t2.join();
    t3.join();
    
    return 0;
}

성능 비교

Busy Waiting vs condition_variable

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <atomic>
#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
// Busy Waiting
std::atomic<bool> ready1{false};
void workerBusy() {
    while (!ready1) {
        // CPU 소모
    }
    std::cout << "Busy Waiting 완료" << std::endl;
}
// condition_variable
std::condition_variable cv;
std::mutex mtx;
bool ready2 = false;
void workerCV() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return ready2; });
    std::cout << "condition_variable 완료" << std::endl;
}
int main() {
    auto start1 = std::chrono::high_resolution_clock::now();
    std::thread t1(workerBusy);
    std::this_thread::sleep_for(std::chrono::seconds(1));
    ready1 = true;
    t1.join();
    auto end1 = std::chrono::high_resolution_clock::now();
    auto time1 = std::chrono::duration_cast<std::chrono::milliseconds>(end1 - start1).count();
    
    auto start2 = std::chrono::high_resolution_clock::now();
    std::thread t2(workerCV);
    std::this_thread::sleep_for(std::chrono::seconds(1));
    {
        std::lock_guard<std::mutex> lock(mtx);
        ready2 = true;
    }
    cv.notify_one();
    t2.join();
    auto end2 = std::chrono::high_resolution_clock::now();
    auto time2 = std::chrono::duration_cast<std::chrono::milliseconds>(end2 - start2).count();
    
    std::cout << "Busy Waiting: " << time1 << "ms (CPU 100%)" << std::endl;
    std::cout << "condition_variable: " << time2 << "ms (CPU 0%)" << std::endl;
    
    return 0;
}

결과:

방법CPU 사용률전력 소모
Busy Waiting100%높음
condition_variable0%낮음
결론: condition_variable이 효율적

실무 사례

사례 1: 스레드 풀

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <condition_variable>
#include <functional>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>
class ThreadPool {
private:
    std::vector<std::thread> workers_;
    std::queue<std::function<void()>> tasks_;
    std::mutex mtx_;
    std::condition_variable cv_;
    bool stop_ = false;
    
public:
    ThreadPool(size_t numThreads) {
        for (size_t i = 0; i < numThreads; ++i) {
            workers_.emplace_back([this]() {
                while (true) {
                    std::function<void()> task;
                    
                    {
                        std::unique_lock<std::mutex> lock(mtx_);
                        cv_.wait(lock, [this]{ return !tasks_.empty() || stop_; });
                        
                        if (stop_ && tasks_.empty()) {
                            return;
                        }
                        
                        task = std::move(tasks_.front());
                        tasks_.pop();
                    }
                    
                    task();
                }
            });
        }
    }
    
    ~ThreadPool() {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            stop_ = true;
        }
        cv_.notify_all();
        
        for (auto& worker : workers_) {
            worker.join();
        }
    }
    
    void enqueue(std::function<void()> task) {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            tasks_.push(std::move(task));
        }
        cv_.notify_one();
    }
};
int main() {
    ThreadPool pool(4);
    
    for (int i = 0; i < 10; ++i) {
        pool.enqueue([i]() {
            std::cout << "작업 " << i << " 실행" << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        });
    }
    
    std::this_thread::sleep_for(std::chrono::seconds(2));
    
    return 0;
}

사례 2: 이벤트 시스템

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <condition_variable>
#include <functional>
#include <iostream>
#include <mutex>
#include <queue>
#include <string>
#include <thread>
struct Event {
    std::string type;
    int data;
};
class EventSystem {
private:
    std::queue<Event> events_;
    std::mutex mtx_;
    std::condition_variable cv_;
    bool stop_ = false;
    
public:
    void emit(const Event& event) {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            events_.push(event);
        }
        cv_.notify_one();
    }
    
    void processEvents() {
        while (true) {
            Event event;
            
            {
                std::unique_lock<std::mutex> lock(mtx_);
                cv_.wait(lock, [this]{ return !events_.empty() || stop_; });
                
                if (stop_ && events_.empty()) {
                    return;
                }
                
                event = events_.front();
                events_.pop();
            }
            
            std::cout << "이벤트: " << event.type << ", 데이터: " << event.data << std::endl;
        }
    }
    
    void shutdown() {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            stop_ = true;
        }
        cv_.notify_all();
    }
};
int main() {
    EventSystem system;
    
    std::thread processor(&EventSystem::processEvents, &system);
    
    system.emit({"click", 100});
    system.emit({"keypress", 65});
    system.emit({"resize", 800});
    
    std::this_thread::sleep_for(std::chrono::seconds(1));
    system.shutdown();
    
    processor.join();
    
    return 0;
}

사례 3: 세마포어

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
class Semaphore {
private:
    std::mutex mtx_;
    std::condition_variable cv_;
    int count_;
    
public:
    Semaphore(int count) : count_(count) {}
    
    void acquire() {
        std::unique_lock<std::mutex> lock(mtx_);
        cv_.wait(lock, [this]{ return count_ > 0; });
        --count_;
    }
    
    void release() {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            ++count_;
        }
        cv_.notify_one();
    }
};
void worker(int id, Semaphore& sem) {
    sem.acquire();
    
    std::cout << "스레드 " << id << " 작업 중" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
    
    sem.release();
}
int main() {
    Semaphore sem(2);  // 최대 2개 스레드
    
    std::vector<std::thread> threads;
    for (int i = 0; i < 5; ++i) {
        threads.emplace_back(worker, i, std::ref(sem));
    }
    
    for (auto& t : threads) {
        t.join();
    }
    
    return 0;
}

트러블슈팅

문제 1: Spurious Wakeup

증상: 조건 없이 깨어남 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 조건 없이 wait
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock);  // 가짜 깨어남 가능
if (ready) {  // 수동 체크
    // ...
}
// ✅ 조건과 함께 wait
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; });  // 조건 자동 체크

문제 2: Lost Wakeup

증상: notify를 놓침 다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ notify 전에 wait
// 스레드 1
ready = true;
cv.notify_one();  // wait 전에 호출
// 스레드 2
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; });  // 이미 notify 지나감
// ✅ 조건 변수와 플래그 함께 사용
// 스레드 1
{
    std::lock_guard<std::mutex> lock(mtx);
    ready = true;
}
cv.notify_one();
// 스레드 2
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; });  // ready가 true면 즉시 통과

문제 3: 데드락

증상: 스레드가 영원히 대기 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ lock 없이 notify
ready = true;  // 경쟁 조건
cv.notify_one();
// ✅ lock으로 보호
{
    std::lock_guard<std::mutex> lock(mtx);
    ready = true;
}
cv.notify_one();

문제 4: lock_guard vs unique_lock

증상: 컴파일 에러 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ lock_guard는 wait에 사용 불가
std::lock_guard<std::mutex> lock(mtx);
cv.wait(lock);  // 에러: lock_guard는 unlock 불가
// ✅ unique_lock 사용
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock);  // OK: unique_lock은 unlock 가능

마무리

condition_variable스레드 간 이벤트 통지를 효율적으로 처리합니다.

핵심 요약

  1. condition_variable
    • 스레드 간 이벤트 통지
    • wait: 조건이 참일 때까지 대기
    • notify_one/notify_all: 대기 스레드 깨우기
  2. 사용법
    • unique_lock과 함께 사용
    • 조건 검사 필수 (Spurious Wakeup 방지)
    • lock으로 조건 변수 보호
  3. 패턴
    • 생산자-소비자
    • 작업 큐
    • 배리어
    • 세마포어
  4. 성능
    • Busy Waiting보다 효율적
    • CPU 사용률 낮음
    • 전력 소모 적음

선택 가이드

상황권장이유
생산자-소비자condition_variable효율적
작업 큐condition_variableCPU 절약
배리어std::barrier (C++20)표준
세마포어std::counting_semaphore (C++20)표준

코드 예제 치트시트

다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 기본 사용
std::condition_variable cv;
std::mutex mtx;
bool ready = false;
// wait
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; });
// notify
{
    std::lock_guard<std::mutex> lock(mtx);
    ready = true;
}
cv.notify_one();
// wait_for
if (cv.wait_for(lock, std::chrono::seconds(1), []{ return ready; })) {
    // 조건 충족
} else {
    // 타임아웃
}

다음 단계

참고 자료

  • “C++ Concurrency in Action” - Anthony Williams
  • cppreference: https://en.cppreference.com/w/cpp/thread/condition_variable
  • POSIX pthread 문서 한 줄 정리: condition_variable은 스레드 간 이벤트 통지를 효율적으로 처리하며, Busy Waiting보다 CPU 사용률이 낮고 전력 소모가 적다.

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3