//

#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개 밖에 빼지 못함.

condition_variable

image.png

(6에서 조건실패하여 락해제, 스레드 블락되었다가 다시 통지를 받고 깨어났으면 락을 걸고 조건 체크함)

구분 condition_variable Windows Event
플랫폼 모든 OS Windows 전용
동기화 범위 스레드 간 스레드/프로세스 간
코드 스타일 C++ 표준, 직관적 WinAPI, 복잡할 수 있음
조건 기반 대기 지원(람다 등) 직접 구현 필요
예외 안전성 높음(RAII) 직접 관리 필요

추가로, 윈도우 이벤트는 DuplicateHandle (핸들복사)나 이름을 지정하는 방법으로 프로세스간 이벤트를 공유할 수 있다.

condition_variable과 Event의 성능 비교

일반적으로 condition_variable은 C++표준라이브러리에서 제공하는 유저모드 동기화 객체로, 일반적으로 커널오브젝트인 윈도우 이벤트보다 빠르다고 한다.

그런데 윈도우는 WakeConditionVariable, SleepConditionVariableCS 를 사용하거나, 이벤트 오브젝트를 활용하여 스레드 대기/깨우기를 처리한다고 한다. 스레드를 block/running 시키는 것은 유저모드에서 불가능하며 이는 OS의 도움이 있어야 한다. 그런데 빠르다는 얘기가 나오는 이유는?

→ C++표준 라이브러리 구현체들은 내부적으로 커널 진입을 최소화하도록 최적화되어 있다고는 하는데.. 잘 와닿지 않는다.

image.png

소비자스레드에서는 통지받고 락 걸고 조건체크, 조건 실패 시 락해제, 스레드 블락.

보통 윈도우 이벤트보다 빠르다고 한다.