앞선 글에서는 락을 획득하는 2가지 방법을 알아보았다. 스핀락(내 차례가 올 때까지 무한정 대기)과 Sleep을 이용한 방법(락 획득 실패 시 타임슬라이스 반환)이다.
이번 글에서는 이벤트를 활용한 방법을 알아볼 것이다.
앞선 글과 같이 화장실에 비유해 본다면 이벤트를 활용한 방법은 다른 누군가에게 '화장실이 비어있으면 나에게 알려줘'라고 부탁하는 것이다.
#include <iostream>
#include <thread>
#include <mutex>
mutex m;
queue<int> q;
void Producer()
{
while (true)
{
{
unique_lock<mutex> lock(m);
q.push(10);
}
this_thread::sleep_for(100ms);
}
}
void Consumer()
{
while (true)
{
unique_lock<mutex> lock(m);
if (q.empty() == false)
{
int data = q.front();
q.pop();
cout << data << endl;
}
}
}
int main()
{
thread t1(Producer);
thread t2(Consumer);
t1.join();
t2.join();
}
0.1초마다 10이라는 데이터를 큐에 넣는 Producer와 그것을 소비하는 Consumer가 있다. 전혀 이상할 것이 없는 코드이지만 만약 Producer가 0.1초가 아니라 굉장히 긴 시간에 한 번씩 데이터를 추가한다고 생각해보자. 그것을 알지 못하는 Consumer는 무한 루프를 돌며 계속해서 확인을 하게 되는데 데이터가 추가되지 않은 상황에서 이렇게 락을 획득하고 검사를 하는 작업은 오류가 일어나지는 않지만 cpu만 차지하는 불필요한 작업이 될 것이다.
이러한 상황에서는 무작정 기다리기보단 데이터가 들어왔을 때만 Consumer에게 알려주어 일을 하게 하면 될 것이다.
#include <iostream>
#include <thread>
#include <mutex>
#include <Windows.h>
mutex m;
queue<int> q;
HANDLE handle;
void Producer()
{
while (true)
{
{
unique_lock<mutex> lock(m);
q.push(10);
}
SetEvent(handle); // 이벤트의 상태가 Signal로 바뀜
this_thread::sleep_for(100ms);
}
}
void Consumer()
{
while (true)
{
//Non-Signal일 때 Sleep
WaitForSingleObject(handle, INFINITE);
//ResetEvent(handle);
//ManualReset이 FALSE이므로 자동으로 Non-Signal로 돌아옴
unique_lock<mutex> lock(m);
if (q.empty() == false)
{
int data = q.front();
q.pop();
cout << data << endl;
}
}
}
int main()
{
// 커널 오브젝트
// Usage Count
// Auto / Manual << bool
// Signal / Non-Signal << bool
handle = CreateEvent(NULL/*보안속성*/, FALSE/*bManualReset*/, FALSE/*bInitialState*/, NULL);
thread t1(Producer);
thread t2(Consumer);
t1.join();
t2.join();
CloseHandle(handle);
}
이벤트를 사용하여 코드를 변경해 보았다. CreateEvent를 이용해 이벤트를 만들 수 있고 핸들이라는 것을 반환해 준다. 이벤트가 하나만 있다고 보장할 수 없기 때문에 핸들은 어떤 이벤트를 의미하는지 알기 위한 식별자라고 볼 수 있다.
CreateEvent에서 중요한 속성은 2, 3번째 속성이다. 1, 4번째 속성은 각각 보안, 이벤트 이름으로 지금 중요한 부분은 아니다. 2번째 속성은 자동으로 리셋을 시켜주는지에 관한 속성으로 FLASE일 경우 자동으로 리셋이 되며 TRUE로 설정할 경우 ResetEvent(handle)과 같이 수동으로 리셋을 해주어야 한다. 3번째 속성은 이벤트의 초기 상태로 위의 경우 FALSE이므로 Non-Signal상태이다.
WaitForSingleObject()은 인자로 핸들과 대기 시간을 줄 수 있는데 위의 상황에서는 데이터가 들어올 때까지 일을 할 필요가 없으므로 INFINITE를 주었다.
Producer가 데이터를 생산한 후 이벤트의 상태를 Signal로 바꾸면 Consumer는 이벤트의 상태가 바뀐 것을 확인하고 Sleep에서 깨어나 일을 하게 된다. 이 때 이벤트의 bManualReset 속성이 FALSE이므로 스레드가 깨어난 후 이벤트는 Non-Signal상태로 바뀐다.
'서버 > 멀티스레드' 카테고리의 다른 글
9. Future (0) | 2024.07.21 |
---|---|
8. Condition Variable (0) | 2024.07.20 |
6. Sleep (0) | 2024.07.17 |
5. SpinLock (1) | 2024.07.14 |
4. DeadLock (0) | 2024.07.11 |