icon안동민 개발노트

조건 변수


조건 변수의 개념

 조건 변수는 스레드 간 통신을 위한 동기화 기법으로, 특정 조건이 만족될 때까지 하나 이상의 스레드를 대기 상태로 만들고, 조건이 만족되면 대기 중인 스레드 중 하나 또는 모두를 깨우는 메커니즘을 제공합니다.

 C++에서는 <condition_variable> 헤더에 정의된 std::condition_variable 클래스를 사용하여 조건 변수를 구현합니다.

기본 사용법

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
 
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
 
void worker_thread() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return ready; });
    
    std::cout << "Worker thread is processing data\n";
}
 
void main_thread() {
    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true;
    }
    cv.notify_one();
}
 
int main() {
    std::thread worker(worker_thread);
    std::thread main(main_thread);
    
    worker.join();
    main.join();
    
    return 0;
}

 이 예제에서 worker_threadready 변수가 true가 될 때까지 대기합니다. main_thread에서 readytrue로 설정하고 notify_one()을 호출하여 대기 중인 스레드를 깨웁니다.

wait, notify_one, notify_all

  • wait() : 조건 변수가 통지받을 때까지 현재 스레드를 블록합니다.
  • notify_one() : 대기 중인 스레드 중 하나를 깨웁니다.
  • notify_all() : 대기 중인 모든 스레드를 깨웁니다.

조건 변수와 술어 (Predicate)

 조건 변수를 사용할 때는 항상 술어(조건)를 함께 사용하는 것이 좋습니다.

 이는 허위 각성(spurious wakeup) 문제를 방지합니다.

cv.wait(lock, []{ return condition; });

 이 방식은 다음과 동일합니다.

while (!condition) {
    cv.wait(lock);
}

생산자-소비자 패턴 구현

 조건 변수는 생산자-소비자 패턴을 구현할 때 매우 유용합니다.

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
 
std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;
 
void producer() {
    for (int i = 0; i < 10; ++i) {
        {
            std::lock_guard<std::mutex> lock(mtx);
            data_queue.push(i);
            std::cout << "Produced: " << i << std::endl;
        }
        cv.notify_one();
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}
 
void consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, []{ return !data_queue.empty(); });
        int data = data_queue.front();
        data_queue.pop();
        std::cout << "Consumed: " << data << std::endl;
        if (data == 9) break;
    }
}
 
int main() {
    std::thread prod(producer);
    std::thread cons(consumer);
    
    prod.join();
    cons.join();
    
    return 0;
}

타임아웃 처리

 wait_for()wait_until() 함수를 사용하여 조건 변수 대기에 타임아웃을 설정할 수 있습니다.

if (cv.wait_for(lock, std::chrono::seconds(5), []{ return ready; })) {
    // 조건이 만족됨
} else {
    // 타임아웃 발생
}

연습 문제

  1. 다중 생산자-다중 소비자 큐를 구현하세요. 여러 생산자 스레드와 여러 소비자 스레드가 동시에 작업할 수 있어야 합니다.
  2. 조건 변수를 사용하여 간단한 작업 스케줄러를 구현하세요. 작업을 추가하고 실행할 수 있어야 합니다.
  3. 세마포어를 조건 변수를 사용하여 구현하세요.

 참고자료

  • C++ Concurrency in Action (2nd Edition) by Anthony Williams
  • The Art of Multiprocessor Programming by Maurice Herlihy and Nir Shavit
  • C++ 표준 문서의 조건 변수 관련 섹션
  • "Operating Systems : Three Easy Pieces" by Remzi H. Arpaci-Dusseau and Andrea C. Arpaci-Dusseau (특히 동기화 관련 장)
  • CppCon 발표 영상들 - 동기화와 조건 변수 관련 세션들