락이 걸렸을때, 스레드 차원에서 대응방법은?
무작정 기다린다. (스핀락)
다른일을 하다가 다시 시도한다. (일정시간 Sleep)
3. 락이 끝났을때 통보 받기. (이벤트를 사용하는 방법)
3번, 이벤트에 대해 알아본다.
아래는 생산자-소비자 스레드 예제로, 생산자 스레드에서는 락을걸고 큐에 데이터를 넣고 소비자 스레드에서는 락을 걸고 큐에 데이터를 꺼내 특정 작업을 한다고 가정한다.
//
#include <mutex>
#include <atomic>
#include <chrono>
#include <thread>
#include <iostream>
#include <queue>
using namespace std;
mutex m;
queue<int> q;
// 생산자 스레드
void Producter()
{
while (true)
{
{
// 락을걸고 q에 데이터를 넣음
unique_lock <mutex> lock(m);
q.push(100);
}
this_thread::sleep_for(10000ms);
}
}
// 소비자 스레드
void Consumer()
{
while (true)
{
// 락을걸고 q에서 데이터를 꺼냄
unique_lock<mutex> lock(m);
if (q.empty() == false)
{
int data = q.front();
q.pop();
//특정 작업을 한다고 가정
cout << data << '\\n';
}
}
}
int main()
{
thread t1(Producter);
thread t2(Consumer);
t1.join();
t2.join();
return 0;
}
진단 도구를 보면, CPU사용률은 5%. 예제를 진행한 컴퓨터는 20코어 이므로 하나의 스레드만 수행되고 있는것을 확인할 수 있다. 여기서 수행되는 스레드는 소비자 스레드로, 이는 CPU리소스 낭비다.
즉 데이터가 있을때만 소비자 스레드에게 알려줘서 일을 진행하면 좋을 것.
//
#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;
// 생산자 스레드
void Producter()
{
while (true)
{
{
// 락을걸고 q에 데이터를 넣음
unique_lock <mutex> lock(m);
q.push(100);
}
::SetEvent(handle); // 이벤트를 시그널상태로 바꾸기
this_thread::sleep_for(10000ms);
}
}
// 소비자 스레드
void Consumer()
{
while (true)
{
::WaitForSingleObject(handle, INFINITE);
// 수동리셋이라면 ResetEvent 호출해야함
// 락을걸고 q에서 데이터를 꺼냄
unique_lock<mutex> lock(m);
if (q.empty() == false)
{
int data = q.front();
q.pop();
//특정 작업을 한다고 가정
cout << data << '\\n';
}
}
}
int main()
{
// 커널 오브젝트
// Usage Count
// Signal / Non-Signal
// Auto , Manual Reset
handle = ::CreateEvent(NULL, FALSE, FALSE, NULL); // 이벤트 생성
thread t1(Producter);
thread t2(Consumer);
t1.join();
t2.join();
::CloseHandle(handle);
return 0;
}
윈도우 이벤트 커널오브젝트를 적용한 예제. 이제 할일이 있을때 생산자 스레드가 알려주면, 소비자 스레드에서 이벤트를 확인해 일을 처리하는 구조가 되었다.
다시 측정해보면, CPU사용률이 0%인것을 확인할 수 있다.