//
#include <mutex>
#include <atomic>
#include <chrono>
#include <thread>
#include <iostream>
#include <queue>
#include <windows.h>
using namespace std;
mutex m;
queue<int> q;
HANDLE handle;
condition_variable cv; //유저레벨 오브젝트
//condition_variable_any cv;
// 생산자 스레드
void Producter()
{
while (true)
{
// 1) Lock을 잡고
// 2) 공유 자원을 수정한다.
// 3) Lock을 풀고
// 4) 조건변수를 통해 다른 쓰레드에게 통지
{
unique_lock <mutex> lock(m);
q.push(100);
}
cv.notify_one(); // wait중인 쓰레드가 있으면 1개만 깨운다.
//::SetEvent(handle);
}
}
// 소비자 스레드
void Consumer()
{
while (true)
{
unique_lock<mutex> lock(m);
cv.wait(lock, []() {return q.empty() == false; });
//::WaitForSingleObject(handle, INFINITE);
// 1) Lock을 잡고
// 2) 조건 확인 (cv.notify_one()호출 + empty())
// 조건O -> 빠져나와서 코드 수행
// 조건X -> Lock을 풀고 대기상태로 전환한다.
// notify_one을 호출했다면, 항상 조건에 만족하지 않을까?
// 생산자 스레드에서 push하고 notify_one을 호출하지만,
// 스레드가 여러개라면 이미 다른스레드가 데이터를 빼갔을 수 있음
// notiy_one할때 lock을 잡고 있는것이 아니기 때문.
// 이를 Spurious Wakeup(가짜 기상) 이라 한다.
// while(q.empty() == false) // 생략가능
{
int data = q.front();
q.pop();
//특정 작업을 한다고 가정
cout << data << '\\n';
}
}
}
int main()
{
thread t1(Producter);
thread t2(Consumer);
t1.join();
t2.join();
::CloseHandle(handle);
return 0;
}
위 예제는 12장에서 알아본 Event예제. 이 예제는 불안정한 부분이 있다.
생산자 스레드가 좀더 빨리돌아서 데이터가 2개가 쌓였을 때, 소비자스레드에서는 1개 밖에 빼지 못함.
(6에서 조건실패하여 락해제, 스레드 블락되었다가 다시 통지를 받고 깨어났으면 락을 걸고 조건 체크함)
구분 | condition_variable | Windows Event |
---|---|---|
플랫폼 | 모든 OS | Windows 전용 |
동기화 범위 | 스레드 간 | 스레드/프로세스 간 |
코드 스타일 | C++ 표준, 직관적 | WinAPI, 복잡할 수 있음 |
조건 기반 대기 | 지원(람다 등) | 직접 구현 필요 |
예외 안전성 | 높음(RAII) | 직접 관리 필요 |
추가로, 윈도우 이벤트는 DuplicateHandle (핸들복사)나 이름을 지정하는 방법으로 프로세스간 이벤트를 공유할 수 있다.
일반적으로 condition_variable은 C++표준라이브러리에서 제공하는 유저모드 동기화 객체로, 일반적으로 커널오브젝트인 윈도우 이벤트보다 빠르다고 한다.
그런데 윈도우는 WakeConditionVariable, SleepConditionVariableCS 를 사용하거나, 이벤트 오브젝트를 활용하여 스레드 대기/깨우기를 처리한다고 한다. 스레드를 block/running 시키는 것은 유저모드에서 불가능하며 이는 OS의 도움이 있어야 한다. 그런데 빠르다는 얘기가 나오는 이유는?
→ C++표준 라이브러리 구현체들은 내부적으로 커널 진입을 최소화하도록 최적화되어 있다고는 하는데.. 잘 와닿지 않는다.
소비자스레드에서는 통지받고 락 걸고 조건체크, 조건 실패 시 락해제, 스레드 블락.
보통 윈도우 이벤트보다 빠르다고 한다.