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에서는 람다를 이용해 한번 더 조건을 확인하고 수행하게된다.
'MultiThread' 카테고리의 다른 글
APC와 Alertable wait 상태에 대한 정리 (0) | 2021.12.29 |
---|---|
Reader Writer Lock 구현 (0) | 2021.12.13 |
기초적인 Lock Free Stack 구현 (0) | 2021.12.03 |
CAS(Compare and Swap)와 SpinLock 구현 (0) | 2021.11.02 |
RAII 패턴과 LockGuard 방식의 스레드 제어 (0) | 2021.10.27 |