이전에 작성한 코드에서 queue size를 출력하면 계속해서 사이즈가 늘어나는 것을 볼 수 있다.
void Producer()
{
while (true)
{
{
unique_lock<mutex> lock(m);
q.push(100);
}
::SetEvent(handle);
}
}
void Consumer()
{
while (true)
{
::WaitForSingleObject(handle, INFINITE);
unique_lock<mutex> lock(m);
if (q.empty() == false)
{
int32 data = q.front();
q.pop();
cout << q.size() << 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);
}
이러한 결과가 나오는 이유는 SetEvent와 코드 나머지 부분, WaitEvent와 코드 나머지 부분이 분리 돼 실행되기 때문이다. 그런데 SetEvent와 WaitEvent를 lock잡고 묶어서 실행해도 문제는 해결되지 않는다. 따라서 쓰레드가 다르기 때문에 코드 실행부가 분리되어 예상한 결과가 나오지 않는다는 설명이 타당해 보인다.
conditio variable을 이용하여 같은 코드를 짜보자.
// 참고) CV는 User-Level Object (커널 오브젝트X)
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중인 쓰레드가 있으면 딱 1개를 깨운다
}
}
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
// notify_one할 때 lock을 잡고 있는 것이 아니기 때문에
//if (q.empty() == false)
{
int32 data = q.front();
q.pop();
cout << q.size() << endl;
}
}
}
int main()
{
thread t1(Producer);
thread t2(Consumer);
t1.join();
t2.join();
}
그런데 이런 경우에도 size는 무한정 늘어난다. 이유는 앞과 같다. 하지만 위의 WindowAPI보다 condition_variable을 쓰는게 바람직한데, C++표준이기 때문이다.
참조 : [C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버 - 인프런 | 강의 (inflearn.com)
댓글 영역