Condition Variable

김 무무 ㅣ 2024. 10. 16. 10:28

1. Condition Variable이란

멀티스레딩 환경에서 스레드 간 시그널링 메커니즘을 제공하는 동기화 도구이다.

주로 특정 조건이 만족될 때까지 스레드를 대기시키고, 조건이 만족되면 대기 중인 스레드에게 알림을 보내는 데 사용된다.

 

특징:

- 스레드 간 통신을 가능하게 함

- 대기(wait)와 통지(notify) 작업을 지원

- 일반적으로 뮤텍스(mutex)와 함께 사용

 

 

2. 사용법

Condition Variable은 race condition을 방지하고 안전한 상태 검사를 보장하기 위해 항상 Mutex와 함께 사용된다.

 

사용 예시:

1) Mutex를 잠근다.

2) 조건을 검사한다.

3) 조건이 만족되지 않으면 Condition Variable의 wait을 호출한다.

4) 조건이 만족되면 작업을 수행한다.

5) Mutex를 해제한다.

 

주요 동작:

  - wait: 스레드를 대기 상태로 만든다.

  - notify_one: 대기 중인 스레드 중 하나를 깨운다.

  - notify_all: 대기 중인 모든 스레드를 깨운다.

 

ex)

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
mutex mtx;
condition_variable cv;
bool ready = false;

void WorkerThread() {
    unique_lock<mutex> lock(mtx);
    cout << "Worker : 대기\n";
    cv.wait(lock, [] { return ready; });

    cout << "Worker : 실행\n";
}

void SignalThread() {
    this_thread::sleep_for(chrono::seconds(2));

    {
        lock_guard<mutex> lock(mtx);
        ready = true;
    }

    cout << "Signal : 신호 전송\n";
    cv.notify_one();
}

int main() {
    thread worker(WorkerThread);
    thread signaler(SignalThread);

    worker.join();
    signaler.join();
}

 

위 예시에서 WorkerThread는 ready가 true가 될때까지 대기하고, SignalThread에서 신호를 보내면 다시 작업을 실행한다.

 

 

3. 응용

다음은 동기화 방법에 대한 대표적인 문제인 생산자-소비자 문제이다.

생산자는 buffer에 데이터를 추가하고, 소비자는 데이터를 제거한다.

이 상황에서 데이터가 동시에 사용되지 않도록 Condition Variable을 사용했다.

 

ex)

#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
// 데이터를 10개씩 처리
const unsigned int BUFFER_SIZE = 10;

queue<int> buffer;
mutex mtx;
condition_variable cvProducer;
condition_variable cvConsumer;

void Producer(int id) {
    for (int i = 0; i < 20; i++) {
        unique_lock<mutex> lock(mtx);
        cvProducer.wait(lock, [] { return buffer.size() < BUFFER_SIZE; });

        buffer.push(i);
        cout << "Producer " << id << " produced : " << i << endl;

        lock.unlock();
        cvConsumer.notify_one();
    }
}

void Consumer(int id) {
    for (int i = 0; i < 10; i++) {
        unique_lock<mutex> lock(mtx);
        cvConsumer.wait(lock, [] { return !buffer.empty(); });

        int value = buffer.front();
        buffer.pop();
        cout << "Consumer " << id << " consumed : " << value << endl;

        lock.unlock();
        cvProducer.notify_one();
    }
}

int main() {
    thread prod1(Producer, 1);
    thread prod2(Producer, 2);
    thread cons1(Consumer, 1);
    thread cons2(Consumer, 2);

    prod1.join();
    prod2.join();
    cons1.join();
    cons2.join();
}

 

 

1) cvProducer는 버퍼가 가득 찼을 때 생산자를 대기시킨다.

2) 생산자는 버퍼에 공간이 있을 때만 데이터를 추가한다.

3) cvConsumer는 버퍼가 비었을 때 소비자를 대기시킨다.

4) 소비자는 버퍼에 데이터가 있을 때만 데이터를 제거한다.

'멀티스레딩' 카테고리의 다른 글

스레드 풀(Thread Pool)  (0) 2024.10.17
고급 동기화 기법  (1) 2024.10.16
C++ 비동기 프로그래밍  (0) 2024.10.15
스핀락(Spinlock)  (0) 2024.10.12
데드락(Deadlock)  (1) 2024.10.07