블로그 이미지
대갈장군

calendar

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

Notice

2012. 12. 13. 05:40 프로그래밍

앞서 커널 오브젝트에 대해서 알아보았는데 이중에 가장 사용하기 쉬운 녀석이 이벤트 오브젝트이다. 


우선 이벤트 오브젝트를 설명하기 앞서 대기 함수에 대해서 설명하자. 대기 함수라 함은 인자로 전달한 커널 오브젝트가 시그널 상태 (파란불)가 될 때까지 (혹은 타임 아웃 제한 시간동안) 이 함수를 호출한 스레드를 대기상태로 빠뜨린다. 


바로 WaitForSingleObject() 함수이다. 원형은 다음과 같다.

DWORD WINAPI WaitForSingleObject(
  _In_  HANDLE hHandle,
  _In_  DWORD dwMilliseconds
);

첫번째 인자로 전달된 hHandle (커널 오브젝트의 핸들)이 시그널 상태가 될때까지 기다리되 두번째 인자로 전달된 시간만큼 최대한 기다렸다가 만약 제한 시간안에 소식이 없으면 그냥 포기하고 대기 상태를 푼다.


커널 오브젝트가 정상적으로 시그널 상태가 되어 대기 함수를 깨우는 경우에는 WaitForSingleObject() 함수의 리턴 값은 WAIT_OBJECT_0가 된다. 만약 기다리다 지쳐 포기하고 리턴하는 경우네는 WAIT_TIMEOUT이 리턴된다. 


고로 다음과 같은 형식의 코드로 작성이 가능하다.


DWORD dwWaitResult;

dwWaitResult = WaitForSingleObject(ghReadyEvent, 5000);

 

if (dwWaitResult == WAIT_OBJECT_0) 

{


}


자 그러면 이제 대표적인 커널 오브젝트인 이벤트 오브젝트를 이용하는 방법을 보자. 우선 이벤트 오브젝트를 생성하는 방법은 다음과 같다.

HANDLE WINAPI CreateEvent(
  _In_opt_  LPSECURITY_ATTRIBUTES lpEventAttributes,
  _In_      BOOL bManualReset,
  _In_      BOOL bInitialState,
  _In_opt_  LPCTSTR lpName
);


여기서 주의해야 할 부분은 두 번째 인자와 세 번째인데 이것은 '수동 리셋 이벤트 (TRUE)' 로 이벤트 오브젝트를 만들지 아니면 '자동 리셋 이벤트 (FALSE)'로 만들지 설정하는 부분이다. 세 번째 인자는 초기 상태를 시그널 상태 (TRUE)로 만들지 아니면 논시그널 상태 (FALSE)로 만들지 설정하는 부분이다.


수동 리셋 이벤트는 시그널 상태가 되면 대기중인 모든 스레드가 시그널 상태가 되는 반면 자동 리셋 이벤트를 대기중인 스레드 중 한개만 신호상태가 되어 CPU를 점유하고 나머지는 계속 대기한다.


일반적으로 시작하면서 바로 파란불로 만드는 경우는 드물기 때문에 세 번째 인자는 FALSE로 간다고 보고 두 번째 인자는 사용자의 의도에 따라 TRUE 혹은 FALSE로 셋팅해야 한다.


그리고 이제 중요한 함수인 시그널 상태 혹은 논시그널 상태로 이벤트 오브젝트를 만들어주는 함수인데 바로 SetEvent() 와 ResetEvent() 함수이다.


SetEvent()와 ResetEvent() 함수 둘 다 커널 오브젝트인 이벤트 오브젝트의 핸들을 인자로 받아 상태를 변경한다. 


이름이 쪼금 헷갈리게 되어 있다. 왜 SetEvent 대신 GoEvent() 라던가 ReadyEvent() 라든지 좀 더 직관적으로 이해 되기 쉬운 함수 이름을 사용 안했는지 모르겠다.


암튼, SetEvent() 함수를 호출하게 되면 전달된 이벤트 오브젝트를 시그널 상태 (즉, 파란불: 준비되었다)로 변경한다. 반면 ResetEvent()는 이벤트 오브젝트를 논시그널 상태 (즉, 빨간불: 아직 멀었어!)로 변경한다.


이제 실제 코드 예제를 보자.


1. 이벤트 오브젝트 핸들 선언

HANDLE ghHDFaceReadyEvent;       


2. 이벤트 오브젝트의 생성 (자동 리셋, 초기 논시그널 상태)

ghHDFaceReadyEvent = CreateEvent(NULL, false, false, TEXT("FRE")); 


3. 작업 스레드에서 해당 이벤트 오브젝트의 시그널 상태를 기다림 (무제한 시간동안)

dwWaitResult = WaitForSingleObject(ghHDFaceReadyEvent, INFINITE); 


4. 필요한 시점에서 해당 이벤트 상태를 시그널 상태로 변경

SetEvent(ghHDFaceReadyEvent);


5. 작업 스레드가 시그널 상태가 된 것을 확인하고 바로 작업에 착수한다. 그리고 모든 일이 끝나면 이벤트를 논시그널 상태로 바꾼다. 물론 자동 리셋 이벤트의 경우 리셋 굳이 안해도 된다. 수동인 경우는 해줘야 다음 루프에서 대기 없이 바로 작업에 착수하는 것을 막을 수 있다.

if (dwWaitResult == WAIT_OBJECT_0) 

{

    ResetEvent(ghHDFaceReadyEvent);

}


스레드의 동기화가 필요한 프로그래밍에서는 이벤트를 이용한 동기화가 제법 효과적이다. 테스트를 해보진 않았지만 프로세스간에 커널 오브젝트가 공유 되므로 서로 다른 프로그램끼리 같은 이벤트 핸들을 이용해서 서로 작업이 완료되었거나 준비되었음을 알려주면 아주 멎진 멀티-프로세스 프로그램을 만들수 있을 것 같다. 하지만 굳이 그렇게 분리된 형태의 프로그램을 작성해야 할 필요가 있나 하는 의문이 들긴 하지만... 뭐 어쨌든 된다고 하니까 어딘가 필요하지 싶다. 


다만 주의해야 할 것은 대기함수를 호출할때 대기 시간을 무제한으로 하게 되면 코딩의 오류나 어떤 문제로 인해 이벤트 오브젝트가 시그널 상태가 영원히 되지 않으면 대기함수를 호출한 스레드는 영원히 잠자게 된다. 데드락은 아니지만 대략 좋지 않은 상태다... 이걸 주의 해야 한다.


posted by 대갈장군