Post

대기하는 쓰래드 모두 깨우기 (Java의 notifyAll)

주의 사항

이 글은 예전 블로그에서 옮겨온 오래 된 글입니다. 현재 상황과는 다를 수 있으며, 잘못 된 정보가 있을 수 있습니다.


Windows Thread API 는 고성능이지만 치명적인 단점이 하나 있(었)는데… 바로 대기하는 Thread를 몽땅 한번에 다 깨우는 방법이 없다는 겁니다. Event 객체는 유용하지만 하나의 쓰래드만 깨우거나(오토리셋 이벤트) 아니면 락이 다 풀리거나(메뉴얼리셋 이벤트) 둘중 하나의 동작만을 하게됩니다.

Java에 익숙하신 분이라면 notifyAll을 유용하게 사용하셨을 거고 pthread 라이브러리를 쓰시는 분도 역시 자주 사용 하실 겁니다.

그래서 거기에 대한 몇가지 꼼수를 프로그래밍 하다 보면 만나볼 수 있는데

  1. 무한 루프 돌면서 상태를 체크한다

    • CPU 낭비도 이만한게 없습니다.
  2. 메뉴얼 리셋 이벤트를 이용해서 잠깐 락을 풀고 다시 건다

    • 락이 걸리지 않고 그냥 지나칠 가능성이 있어 위험합니다.
  3. pthread나 ACE에 포팅된 상태변수를 가져다 쓴다 해결책이 아니잖아

실제로 ACE는 상태변수를 자체 구현하고 있는데 그 대략적인 방법은 아래와 같습니다.

  1. 오토 리셋 이벤트와 크리티컬 섹션을 생성한다

  2. join 할 때 마다 카운트를 증가시킨다

  3. 이벤트 발생시 증가된 카운트만큼 이벤트를 Set 한다.

뭐 그리 복잡한 방법도 아니고 구현 자체는 어렵지 않은데… 어쨋든 OS의 부족한 기능을 외부에서 가져오자니 좀 그렇습니다.

어쨋든 Windows Vista/2008 부터는 여기에 대응하는 API가 추가되었습니다. WakeAllConditionVariable라는 놈인데요. 말 그대로 모든놈 다 깨우라는 뜻입니다.

pthread의 상태변수가 그런것 처럼 상태변수 자체만으론 Lock 의 기능을 하지 않고 다른 락의 힘을 빌리게 되는데 기본적으론 Critical Section 을 사용하게 됩니다. 이 다음은 귀찮으니 소스 보면서 하겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <Windows.h>
#include <cstdio>
 
CRITICAL_SECTION cs;
CONDITION_VARIABLE cv;
 
DWORD PASCAL TestThread(LPVOID pParam)
{
    EnterCriticalSection(&cs);
     
    SleepConditionVariableCS(&cv, &cs, INFINITE);
    printf("%d\n", GetCurrentThreadId());
    LeaveCriticalSection(&cs);
 
    return 0;
}
 
int _tmain(int argc, _TCHAR* argv[])
{
    InitializeCriticalSection(&cs);
    InitializeConditionVariable(&cv);
 
    HANDLE Threads[10];
    for(int i = 0; i < 10; ++i)
    {
        Threads[i] = CreateThread(nullptr, 0, &TestThread, nullptr, 0, nullptr);
    }
    Sleep(1000);
    WakeAllConditionVariable(&cv);
    WaitForMultipleObjects(10, Threads, TRUE, INFINITE);
    DeleteCriticalSection(&cs);
    return 0;
}

CriticalSection 에 추가적으로 ConditionVariable 을 초기화 시켜 준 후, WaitForSingleObject 대신 SleepConditionVariableCS을 사용합니다(만약 Read/Write Lock 을 사용중이라면 SleepConditionVariableSRW을 대신 이용합니다)

그리고 Thread가 대기하고 있을때 WakeAllConditionVariable를 이용해서 쓰래드를 몽땅 깨워 주면 됩니다. 당연히 대기중인 Thread가 하나도 없을땐 아무 일도 하지 않습니다.

만약 하나만 깨운다면 WakeConditionVariable를 사용할 수 있습니다. 하나만 깨운다는면에서는 동일하지만 Event 와는 동작이 미묘하게 다른것이 이벤트는 대기중인 Thread가 없어도 Set 상태가 되면 언제라도 통과할 수 있지만 WakeConditionVariable은 대기중인 Thread가 없으면 아무 일도 하지 않습니다.

덤으로 Condition Variable 은 해제 함수가 없습니다. 그냥 냅두시면 됩니다.

This post is licensed under CC BY 4.0 by the author.