메뉴 건너뛰기


Developer > Application

Syncronization 7장. 이벤트(Event) - 윈도우즈

2013.11.16 02:16

푸우 조회 수:10281

7장. 이벤트(Event)
 
이벤트(Event) 동기화 객체는 윈도우즈에서만 사용가능합니다. 아시다 시피 이벤트란 어떤 사건이 일어났음을 알리는 동기화 객체입니다. 크리티컬 섹션, 뮤텍스, 세마포어는 주로 공유자원을 보호하기 위해 사용되는 데 비해 이벤트는 쓰레드간의 작업 순서나 시기를 조정하기 위해 사용됩니다. 또한 6장에서 본 조건변수와 비슷하게 동시에 여러 쓰레드의 대기 상태를 풀어 줄 수도 있습니다.
 
위의 이벤트에 대한 설명을 들으면 윈도우에서 이야기하는 메시지와 같네라고 생각하실 분도 계실 것 같네요. 뭐 개념은 비슷합니다. 하지만 메시지는 윈도우에서 일반적으로 사용자의 동작에 의해 만들어지는 것이라면 쓰레드는 대부분 윈도우가 존재하지 않으며 백그라운드로 동작하기 때문에 사용자의 동작을 받지도 않는 것이 대부분입니다. 그런 이유로 이벤트라는것이 별도로 존재하며 사용법 또한 다릅니다.
 
여기서 한가지 개념을 소개해 드려야 겠네요.
우리는 이미 뮤텍스에서 신호상태 혹은 비신호상태라는 것에 대해 이야기한바가 있습니다. 이 신호상태나 비신호상태를 사용자가 임의로 조절할 수는 없었습니다. 즉, 대기함수에 의해 대기상태가 풀리면 뮤텍스를 바로 비신호상태로 만들어 버리죠. 이런것을 보고 자동리셋모드(Auto Reset Mode)라고 합니다. 그와는 반대로 대기상태가 풀려도 바로 비신호상태로 만들지 않고 사용자가 비신호상태로 만들어줘야 하는 경우를 수동리셋모드(Manual Reset Mode)라고 합니다. 물론 뮤텍스를 수동리셋모드로 사용할 수 있다는 것은 아닙니다. 지금 이런 개념을 이야기 하는 이유는 지금 설명하고 있는 윈도우즈의 이벤트가 이 리셋모드를 변경할 수 있기 때문이죠.
 
이벤트 관련함수로는 다음과 같은 것들이 있습니다.
 
HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName);
HANDLE OpenEvent(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName);
BOOL SetEvent(HANDLE hEvent);
BOOL ResetEvent(HANDLE hEvent);
BOOL PulseEvent(HANDLE hEvent);
 
역시 윈도우즈 커널 동기화 객체이므로 WaitForSingleObject(), CloseHandle()함수도 사용된다는 것은 말 하지 않아도 되겠죠?
그럼 다섯개의 함수만 설명드리면 되겠네요.
 
CreateEvent()함수는 이벤트를 생성할 때 사용합니다. 생성하려는 이름의 이벤트가 이미 존재한다면 그 이벤트 핸들을 리턴합니다.
OpenEvent()함수는 이미 존재하는 명명된 이벤트의 핸들을 얻어올때 사용합니다.
SetEvent()함수는 이벤트를 신호상태로 만듭니다.
ResetEvent()함수는 이벤트를 비신호상태로 만듭니다.
PulseEvent()함수는 이벤트를 신호상태로 만들었다가 비신호상태로 만드는 함수인데 호환성 유지를 위해 제공되고 있을 뿐 지금은 사용할 필요가 없습니다.
 
이미 눈치 채셨겠지만 ResetEvent()함수는 수동리셋모드에서 사용하게 됩니다.
 
CreateEvent()함수의 아큐먼트는 다음과 같습니다.
- lpEventAttributes : 이벤트의 보안 속성을 지정하는 것으로서 주로 상속관계를 지정하기 위해 사용됩니다. 일반적으로는 NULL을 입력합니다.
- bManualReset : 이 값을 TRUE로 지정하면 이벤트가 수동리셋이벤트로 생성되고 FALSE로 입력하면 자동리셋이벤트가 생성됩니다.
- bInitialState : TRUE를 입력하면 이벤트를 생성하자 마자 신호상태의 이벤트를 만듭니다.FALSE를 입력할 경우 비신호상태의 이벤트를 만듭니다.
- lpName : 이벤트의 이름을 문자열로 지어 줍니다. 이름이 지정된 이벤트를 만듦으로서 이 이름을 아는 다른 프로세스와의 동기화를 할 수 있습니다. 하나의 프로세스에서만 동기화 방식으로 이벤트를 사용한다면 NULL을 입력하여 이름을 지어 주지 않을 수도 있습니다.
  
OpenEvent()함수의 아큐먼트는 다음과 같습니다.
- dwDesiredAccess : 접근속성
- bInheritHandle : 상속옵션
- lpName : 지정된 이벤트의 이름
 
 
1. 자동리셋이벤트
 
이미 이야기 한대로 자동리셋 이벤트는 사용자가 이벤트의 상태를 변경할 수 없는 경우를 이야기 합니다. 이미 함수에 대한 설명은 모두 했으니깐... 그냥 예제를 보도록 하겠습니다.
 
#include <stdio.h> #include <process.h> #include <windows.h> int cnt=0; HANDLE hEvent; DWORD WINAPI Thread1(void *arg) { int i, tmp; for(i=0; i<1000; i++){ WaitForSingleObject(hEvent, INFINITE); tmp=cnt; Sleep(1); cnt=tmp+1; SetEvent(hEvent); } printf("Thread1 End\n"); return 0; } DWORD WINAPI Thread2(void *arg) { int i, tmp; for(i=0; i<1000; i++){ WaitForSingleObject(hEvent, INFINITE); tmp=cnt; Sleep(1); cnt=tmp+1; SetEvent(hEvent); } printf("Thread2 End\n"); return 0; } int main(int argc, char *argv[]) { HANDLE hThread[2]; hEvent=CreateEvent(NULL, FALSE, TRUE, NULL); hThread[0]=CreateThread(NULL, 0, Thread1, NULL, 0, NULL); hThread[1]=CreateThread(NULL, 0, Thread2, NULL, 0, NULL); WaitForMultipleObjects(2, hThread, TRUE, INFINITE); printf("%d\n", cnt); CloseHandle(hThread[0]); CloseHandle(hThread[1]); CloseHandle(hEvent); return 0; }
 
코드의 내용자체는 설명하지 않아도 되겠죠?
한가지만 설명드리자면 CreateEvent()함수 호출시에 자동리셋모드로 이벤트를 생성하기 위해 두번째 인자를 FALSE로 주었고 생성하자 마자 이벤트를 신호상태로 만들어 첫번째 이벤트를 사용하려는 쓰레드가 사용할 수 있도록 했습니다. 
 
2. 수동리셋이벤트
 
수동리셋 이벤트는 쓰레드 내에 사용자가 명시적으로 이벤트를 비신호상태로 만들 때까지 신호상태를 유지하는 것을 말합니다. 이것도 예제를 보고 이해 하도록 하죠.
 
#include <stdio.h> #include <process.h> #include <windows.h> int cnt=0; HANDLE hEvent; DWORD WINAPI CalcThread(void *arg) { int i; for(i=0; i<10; i++){ ResetEvent(hEvent); Sleep(10); cnt=i; SetEvent(hEvent); printf("========\n"); } return 0; } DWORD WINAPI Thread1(void *arg) { WaitForSingleObject(hEvent, INFINITE); printf("Thread1 : %d\n", cnt); return 0; } DWORD WINAPI Thread2(void *arg) { WaitForSingleObject(hEvent, INFINITE); printf("Thread2 : %d\n", cnt); return 0; } DWORD WINAPI Thread3(void *arg) { WaitForSingleObject(hEvent, INFINITE); printf("Thread3 : %d\n", cnt); return 0; } int main(int argc, char *argv[]) { HANDLE hThread[3], hCalcThread; hEvent=CreateEvent(NULL, TRUE, FALSE, NULL); hCalcThread=CreateThread(NULL, 0, CalcThread, NULL, 0, NULL); while(WaitForSingleObject(hCalcThread, 1)!=WAIT_OBJECT_0){ hThread[0]=CreateThread(NULL, 0, Thread1, NULL, 0, NULL); hThread[1]=CreateThread(NULL, 0, Thread2, NULL, 0, NULL); hThread[2]=CreateThread(NULL, 0, Thread3, NULL, 0, NULL); WaitForMultipleObjects(3, hThread, TRUE, INFINITE); } printf("Result: %d\n", cnt); CloseHandle(hCalcThread); CloseHandle(hThread[0]); CloseHandle(hThread[1]); CloseHandle(hThread[2]); CloseHandle(hEvent); return 0; }
 
위의 예제는 기존의 예제와는 좀 다르네요. 그런데 사실은 수동리셋이벤트에 대한 좋은 예제를 생각해 내지 못했습니다. 그래서 그냥 신호상태와 비 신호 상태를 제어하는 방법만 보시기 바랍니다.
 
설명을 드리자면 CreateEvent()함수에서 수동리셋이벤트를 만들기 위해서 2번째 인자를 TRUE로 만들고 처음부터 이벤트를 사용하고자 하는 쓰레드들을 대기 상태로 만들기 위해 3번째 인자도 FALSE로 만들었습니다. 
그리고는 CalcThread()를 돌리는데 이 쓰레드는 cnt변수를 0에서 9까지 증가시킵니다.
한번 값을 변경하면 이벤트를 신호상태로 바꿔주고 10밀리세컨 기다린 다음에 다시 비신호상태로 변경하고 있습니다.
이 10밀리세컨 동안 깨어난 다른 세개의 쓰레드가 cnt값을 읽어서 출력합니다.
 
그래서 값이 차례대로 찍히게 되는데...
 
사실 Sleep()의 값을 줄이면 차례대로 찍히지는 않을 수도 있습니다.
어찌되었건 코드내에서 이벤트를 신호상태로 혹은 비신호상태로 변경하고 있는 것은 아시겠죠?
 
뭐 허접하지만 한번 분석해 보시길...
더 좋은 예제를 보시거나 생각해 내신 분은 댓글로 올려주시면 고맙겠습니다.
 
그럼 이벤트 객체에 대한 설명은 여기까지 끝...
 
Creative Commons License
Creative Commons License이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
Copyright 조희창(Nicholas Jo). Some rights reserved. http://bbs.nicklib.com