본문 바로가기
MultiThread

이벤트 방식 Lock 구현 (Condition Variable, Window API)

by W00gie 2021. 11. 4.

Lock을 구현하는 여러 방법 중 이번엔 Event 기반의 Lock이다. sleep과 Event를 응용한다.

Event 방식은 OS에게 커널오브젝트를 넘겨 해당 Lock이 풀렸을 때 signal을 보내고

작업을 기다리는 스레드들은 waitForSingleObject함수를 통해 signal을 받아 동작한다.

// GameServer.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include "pch.h"
#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
#include <windows.h> // for event

mutex m;
queue<int32> q;
HANDLE handle;

void Producer()
{
	while (true) {
		{
			unique_lock<mutex> lock(m);
			q.push(100);
		}

		::SetEvent(handle); //시그널 상태로 변경
		this_thread::sleep_for(100ms);
	}
	
}

//producer가 생성한 정보를 추출해 사용
void Consumer() 
{
	while (true) {
		{
			::WaitForSingleObject(handle, INFINITE); //시그널상태 기다림
			//::ResetEvent(handle)  / Manual Reset 설정했을 경우
			//Auto Reset 으로 핸들을 설정하여 자동으로 non-signal상태로 바뀜


			unique_lock<mutex> lock(m);
			if (q.empty() == false) 
			{
				int32 data = q.front();
				q.pop();
				cout << data << endl;
			}
		}
	}
}

int main() 
{
	//커널 오브젝트
	//Usage Count
	//Signal (파란불) Non -Signal(빨간불_ << bool
	//Auto / Manual << bool
	handle = ::CreateEvent(NULL, FALSE, FALSE, NULL);

	thread t1(Producer);
	thread t2(Consumer);

	t1.join();
	t2.join();


	::CloseHandle(handle); //이벤트 종료

	return 0;
	
}

 

main에서 생성되는 handle 변수는 겉으로 봐선 단순한 int variable 이지만 해당 정수는 이 이벤트를 작업할때 해당 번호로 식별하는 역할을 하게 된다.

 

중요한점은 커널영역까지 진입하는데 다소 높은 퍼포먼스가 필요하다는 점이다.

대기 시간이 길어질 경우 이벤트기반의 lock이 효과적이지만

대기 시간이 짧은 경우 유저레벨에서 처리할 수 있는 SpinLock 방식을 고려해볼 필요가 있다.

 

 

이를 보완한 구현방식은 Condtion Variable을 이용한 Event Lock이다.

#include "pch.h"
#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
#include <windows.h>

mutex m;
queue<int32> q;
HANDLE handle;

condition_variable cv; 


void Producer()
{
	while (true) 
	{

		//1)lock을 잡고
		//2)공유 변수 값을 수정
		//3)lock을 풀고
		//4) 조건변수 통해 다른 스레드들에게 통지
		{
			unique_lock<mutex> lock(m);
			q.push(100);
		}

		cv.notify_one(); //wait 중인 스레드중에 한개를 깨움
		::this_thread::sleep_for(100ms);
	}
	
}

//producer가 생성한 정보를 추출해 사용
void Consumer() 
{
	while (true) {
			unique_lock<mutex> lock(m);
			cv.wait(lock, []() {return q.empty() == false; });
			// 1)lock을 잡고
			// 2)조건 확인
			// -만족o => 빠져나와서 이어서 코드 진행
			// -만족X => Lock을 풀어주고 대기 상태


			//notify one을 했으면 항상 조건식을 만족한다? => spurious wakeup 가짜기상 발생가능
			//cb.wait의 조건함수를 통해 cross check하는것

			{
				int32 data = q.front();
				q.pop();
				cout << q.size() << endl;
			}
	}
}

int main() 
{
	thread t1(Producer);
	thread t2(Consumer);

	t1.join();
	t2.join();

	return 0;
	
}

Condition Variable은 커널영역에 접근할 필요없는 유저 레벨 오브젝트이다.

C++ 표준 기능으로 확립되어 window와 Linux 두 환경에서 모두 작동한다.

주요 함수는 cv.notify와 cv.wait이다. cv.wait에서는 람다를 이용해 한번 더 조건을 확인하고 수행하게된다.