[ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 14. 쓰레드 동기화 기법 2
01. 실행 순서에 있어서의 동기화
앞선 Chapter에서는 임계 영역 접근을 제한하기 위한 동기화 방법을 이야기했다. 이번 Chapter에서는 쓰레드의 실행 순서를 동기화하는 기법에 대해 이야기할 것이다.
앞서 언급했지만, “쓰레드의 실행 순서를 동기화한다”는 것은 정확히 표현하면 다음과 같다.
“메모리에 접근하는 쓰레드의 실행 순서를 동기화한다.”
따라서, “실행 순서 동기화”라는 것은 “메모리 접근 동기화”를 포함하는 개념이다.
생산자/소비자 모델
운영체제 서적을 보면, 보통 “실행 순서의 동기화”를 “생상자/소비자 모델”이라 불리는 쓰레드 모델을 통해서 설명하고 있다.
생산자/소비자 모델에서 중요한 것은 순서이다. 생산자가 무엇인가를 만들어내고, 만들어낸 것을 소비자가 이를 소비한다. 만약 순서가 바뀐다면, 아무것도 없는 곳에서 소비가 이뤄지게 되고, 생산자는 소비가 발생한 후에, 생산하게 된다.
이처럼 생산자/소비자 모델은 실행되는 쓰레드의 순서가 중요한 상황을 설명할 대 종종 소개되는 모델이다. 이러한 모델에 해당되는 간단한 예시로, 문자열을 입력(생성) 하고, 이를 출력(소비) 하는 것이 있다. 이 과정에서는 입력되는 문자열이 출력되는 문자열보다 빨라서, 쓰레드가 감당하지 못하게 되고 이는 문자열의 손실로 이어진다. 이를 해결하기 위해 쓰레드 사이에 버퍼를 두고, 두 쓰레드가 입력 및 출력 속도에 상관없이 독립적으로 실행되도록 한다.
이벤트(Event) 기반 동기화
쓰레드의 실행 순서를 동기화한다고 하면, 가장 먼저 떠올리는 것이 이벤트 기반 동기화 기법이다. 이 기법에서도 동기화를 위한 오브젝트가 사용된다. 이를 이벤트 오브젝트라 한다.
다음은 이벤트를 생성 및 소멸시키는 함수와, 이벤트를 소유 및 반환하는 함수이다. 대신에 앞서 열쇠에 비유한 뮤텍스와 세마포어와는 다르게 상태(Signaled, Non-Signaled)의 개념을 도입하고자 한다. 즉, 상태에 따라서 실행되어야 할 쓰레드를 결정하는 것이다.
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCSTR lpName
);
BOOL ResetEvent(
HANDLE hEvent
);
BOOL SetEvent(
HANDLE hEvent
);
bManualReset: 이 함수의 가장 중요한 전달인자로, 수동 리셋 모드(Manual-Reset)와 자동 리셋 모드(Auto Reset)를 지정한다.
또한, 이는 마찬가지로 CloseHandle을 사용하여 오브젝트를 소멸시킨다.
쓰레드나 프로세스의 커널 오브젝트 경우, 초기에는 Non-Signaled 상태로 생성된다. 그러나 쓰레드나 프로세스가 종료될 경우 해당 커널 오브젝트는 Signaled 상태로 자동 변경된다. 하지만, 이벤트 오브젝트는 자동으로 Signaled 상태가 되지 않는다. 이미 알다시피 Non-Signaled 상태의 이벤트 오브젝트 핸들을 인자로 전달하면서 WaitForSingleObject 함수를 호출한 쓰레드는 블로킹 상태가 된다. 그러나 Signaled 상태가 되면 여느 커널 오브젝트와 마찬가지로 WaitForSingleObject 함수를 반환한다. 만약 이벤트 오브젝트가 Signaled 상태가 되어 블로킹 상태에 있던 쓰레드가 빠져나왔을 때, Signaled 상태 그대로라면 수동 리셋 모드이며, 자동으로 Non-Signaled 상태가 되었다면 자동 리셋 모드이다.
지금까지의 내용을 정리하면 다음과 같다.
이벤트 커널 오브젝트는 프로그래머의 요청에 의해서 Signaled 상태가 된다.
Non-Signaled 상태의 이벤트 오브젝트 때문에 WaitForSingleObject 함수 호출이 블로킹되었다면, Signaled 상태가 되는 순간 블로킹된 함수를 빠져나오게 된다. 이때 자동 리셋 모드라면 Non-Signaled 상태로 자동으로 변경된다.
위 그림의 윗부분은 수동 리셋 모드 이벤트이기 때문에 Signaled 상태에서 Non-Signaled 상태로의 변경을 위해 WaitForSingleObject 함수 호출 이후에 ResetEvent 함수를 호출해야 함을 설명한다. 반대로 아래는 자동 리셋 모드의 이벤트이기 때문에 WaitForSingleObject 함수를 반환하면서 자동으로 Non-Signaled 상태로 돌아간다.
수동 리셋(Manual-Reset) 모드 이벤트(Event)의 활용 예
둘 이상의 쓰레드를 동시에 깨워서 실행해야 할 때 수동 리셋 모드 이벤트가 유용하게 사용된다. 앞서 이야기했던 생산자/소비자 모델에서, 소비자 역할을 하는 쓰레드를 추가한다면 다음과 같은 상황이 벌어질 것이다.
자동 리셋 모드 이벤트일 경우, 문자열을 입력받은 생산자 쓰레드가 이벤트 오브젝트를 Signaled 상태로 변경하면, 하나의 쓰레드만 WaitForSingleObject 함수를 빠져나와 실행을 완성할 수 있다.
그러나, 수동 리셋 모드 이벤트일 경우 두 개의 소비자 쓰레드는 동시에 블로킹 상태를 빠져나와 실행을 재개한다.
자동 리셋 모드일 때보다 원하는 결과와 가까워졌지만, 두 개의 쓰레드가 동시에 실행되기 때문에, 우리가 기대했던 형태로 출력이 이뤄진다는 것을 보장받지 못하고 잘못된 결과를 출력할 수 있다. 이러한 문제를 해결하기 위해 뮤텍스를 사용한다.
02. 이벤트(Event) 더하기 뮤텍스(Mutex)
앞선 예시에서는 이벤트만을 사용하여 둘 이상의 쓰레드를 동시에 깨웠지만, 원하는 결과를 만족할 순 없었다. 하지만, 뮤텍스를 생성하여 출력 결과에 대한 코드블럭을 임계 영역으로 설정하여 뮤텍스를 활용한다면, 100% 원하는 결과를 얻을 수 있게 된다. 즉, 쓰레드를 순서대로 깨우는 것이 가능해진다.
03. 타이머(Timer) 기반 동기화
타이머라는 동기화 오브젝트는 정해진 시간이 지나면 자동으로 Signaled 상태가 되는 특성을 지닌다. 때문이 이름도 타이머(Waitable Timer)이다.
타이머를 기반으로 쓰레드를 동기화한다는 것은 임계 영역 문제 해결을 위한 동기화와는 그 관점이 다르다. 여기서 말하는 동기화는 쓰레드의 실행 시간 및 실행 주기를 결정하겠다는 의미이다.
타이머 기반 동기화는 다음과 같이 두 가지 형태로 구분 지을 수 있다.
수동 리셋 타이머: 가장 일반적인 타이머로, 알람 시계의 특성과 같다. 원하는 시간을 설정하여 실행 시간을 정한다.
주기적 타이머: 수동 리셋 타이머에 주기적인 특성이 가해진 형태이다. 설정한 시간마다 한 번씩 주기적으로 실행된다.
수동 리셋 타이머(Manual-Reset Timer)
타이머 오브젝트는 정해진 시간이 지나야 Signaled 상태가 되는 커널 오브젝트이다. 다음은 타이머 오브젝트를 생성하는 함수이다.
HANDLE CreateWaitableTimer(
LPSECURITY_ATTRIBUTES lpTimerAttributes,
BOOL bManualReset,
LPCSTR lpTimerName
);
bManualReset: 타이머의 수동 리셋 및 자동 리셋 모드를 결정하는 매개변수이다.
BOOL SetWaitableTimer(
HANDLE hTimer,
const LARGE_INTEGER* lpDueTime,
LONG lPeriod,
PTIMERAPCROUTINE pfnCompletionRoutine,
LPVOID lpArgToCompletionRoutine,
BOOL fResume
);
lpDueTime: 알람이 울리는 시간(커널 오브젝트가 Signaled 상태가 되는 시간)을 지정하는 매개변수로, + 값이 전달되면 절대 시간을, - 값이 전달되면 상대 시간을 의미한다.
lPeriod: 타이머가 주기적으로 알람을 울리게 할 때 사용하는 전달인자이다. 0을 전달할 경우, 주기적인 알람을 사용하지 않는다는 의미이다.
추가적으로, CancleWaitable Timer 함수는 가동 중에 있는 타이머를 중지시키는 기능의 함수이며, 정의는 다음과 같다.
BOOL CancelWaitableTimer(
HANDLE hTimer
);
또한, 타이머의 소멸을 원할 경우 커널 오브젝트와 마찬가지로 CloseHandle 함수를 호출하면 된다.
참고 자료:
윤성우. 『뇌를 자극하는 윈도우즈 시스템 프로그래밍』.한빛미디어, 2007.