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 |