Event

 

이벤트란 어떤 사건이 일어났음을 알리는 동기화 객체이다.
크리티컬 섹션, 뮤텍스, 세마포어는 주로 공유 자원을 보호하기 위해 사용되고 이벤트는 스레드 간의 작업 순서나 시기를 조정하고

신호를 보내기 위해 사용된다.

특정한 조건이 만족될 때까지 대기해야 하는 스레드가 있을 경우 이 스레드의 실행을 이벤트로 제어할 수 있다.

 

이벤트를 기다리는 스레드는 이벤트가 신호상태가 될 때까지 대기하며 신호상태가 되면 대기를 풀고 작업을 시작한다.

이때 대기 함수가 대기를 풀면서 이벤트 객체를 어떻게 처리하는가에 따라 두 가지 종류로 구분된다.


 - 자동 리셋 이벤트 : 대기 상태가 종료되면 자동으로 비신호상태가 된다.
 - 수동 리셋 이벤트 : 스레드가 비신호상태로 만들 때까지 신호를 유지한다.

 

하기 두 함수는 이벤트를 만들거나 오픈하는 함수이다.

 

<CreateEvent>

1
2
3
4
5
6
7
8
HANDLE
WINAPI
CreateEvent(
    __in_opt LPSECURITY_ATTRIBUTES lpEventAttributes,
    __in     BOOL bManualReset,
    __in     BOOL bInitialState,
    __in_opt LPCTSTR lpName
    );
cs

 

<OpenEvent>

1
2
3
4
5
6
7
HANDLE
WINAPI
OpenEvent(
    __in DWORD dwDesiredAccess,
    __in BOOL bInheritHandle,
    __in LPCTSTR lpName
    );
cs

 

bManualReset 설정 값이 TRUE이면 수동 리셋 이벤트이고 FALSE이면 자동 리셋 이벤트가 된다.
bInitialState 설정 값이 TRUE이면 이벤트를 생성함과 동시에 신호상태로 만들어 이벤트를 기다리는 스레드가 곧바로 실행된다.
이벤트도 이름을 가지므로 프로세스간의 동기화에 사용될 수 있다.
이벤트가 동기화 객체와 또 다른 점은 대기 함수를 사용하지 않고도 임의적으로 신호상태와 비신호상태를 설정할 수 있다는 점이다.

이때는 다음 함수를 사용한다.

 

1
2
3
BOOL WINAPI SetEvent(__in HANDLE hEvent);
 
BOOL WINAPI ResetEvent(__in HANDLE hEvent);
cs

 

SetEvent는 이벤트를 신호상태로 만들고 ResetEvent는 이벤트를 비신호상태로 만든다.

 

 

Auto Reset Event (자동 리셋 이벤트)

 

멀티 스레드가 진가를 발휘할 때는 백그라운드 작업을 할 때이다.

예를 들어 백그라운드에서는 시간이 걸리는 계산, 인쇄, 다운로드, 자료 정렬 등을 할 때 스레드에게 분담할 수 있다.

 

우선 수만건의 DB 내용을 읽는 과정을 메인 스레드에서 진행하면 어떻게 되는지 확인해보자.

하기 예는 메인 스레드에서 DB 내용을 읽는 과정을 처리하는 코드이다.

 

1
2
3
4
5
6
7
8
9
void DBRead(void)
{
    int i;
    for (i=0; i<1000000++i)
    {
        Sleep(30); //시간이 오래 걸리는 작업임을 나타내기 위한 Sleep.
        buf[i] = i;
    }
}
cs

 

Sleep 함수가 시간이 오래 걸리는 과정이라 생각하고 보면, 메인 스레드는 DBRead 함수에서 DB 내용을 모두 읽을 때까지

기다려야만 한다. 즉, DB Read 과정 동안 메인 스레드는 아무 처리를 하지 못하게 되므로 이런 함수는 하기와 같이 별도의

스레드로 분리해야 한다.

 

1
2
3
4
5
6
7
8
9
DWORD WINAPI ThreadFunc(LPVOID prc)
{
    int i;
    for (i=0; i<1000000++i)
    {
        Sleep(30); //시간이 오래 걸리는 작업임을 나타내기 위한 Sleep.
        buf[i] = i;
    }
}
cs

 

이제 이 스레드는 새로 스택을 생성하고 자신만의 실행 흐름을 가지며 메인 스레드와는 따로 스케줄링된다.

만약 이 계산 작업이 메인 스레드와는 아무런 상관이 없는 단순 백그라운드 작업이라면 아무 문제가 없지만 메인 스레드에

작업 결과를 전달해주어야 한다면 이는 또 다른 문제의 발생 원인이다. 그렇게 되면 결국 메인 스레드는 Read 동작이 완료될

때까지 기다린 후 DB 내용을 화면에 표시해주어야 한다. 결국 메인 스레드에서 함수를 호출한 것과 다를게 하나도 없게 되버린다.


그러나 생각을 바꾸면 모든 데이터를 읽을 때까지 주 스레드가 기다릴 필요없이 당장 출력할 정도만 되면 일단 화면에 표시하고

남은 작업은 백그라운드 스레드가 하도록 맡겨 놓으면 이런 문제를 해결할 수 있다.

 

이런 처리를 하려면 작업 스레드가 어디까지 계산을 했는지 주 스레드에게 알릴 필요가 있으며 이런 통신에 이벤트가 사용된다.

다음 예제는 30개의 DB 내용을 Read한 후에 화면에 출력을 하는 코드이다.

 

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
HANDLE hEvent;
int buf[100];
 
DWORD WINAPI ThreadFunc(LPVOID prc)
{
    int i;
    for (i=0; i<1000000++i)
    {
        Sleep(30);
        buf[i] = i;
        if (i == 30)
            SetEvent(hEvent); //hEvent를 신호상태로 변경.
    }
 
    return 0;
}
 
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    int i;
    TCHAR str[128];
    HDC hdc;
    PAINTSTRUCT ps;
    DWORD ThreadID;
    HANDLE hThread;
    TCHAR *Mes = "마우스 왼쪽 버튼을 눌러 주십시오.";
 
    switch(iMessage) 
    {
    case WM_CREATE:
        hWndMain = hWnd;
        hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); //비신호상태 자동 리셋 이벤트 생성.
        return TRUE;
    case WM_LBUTTONDOWN:
        InvalidateRect(hWnd, NULL, TRUE);
        UpdateWindow(hWnd);
        hThread = CreateThread(NULL0, ThreadFunc, NULL0&ThreadID);
        CloseHandle(hThread);
        WaitForSingleObject(hEvent, INFINITE); //hEvent가 신호상태가 될 때까지 대기.
                                               //ThreadFunc에서 hEvent를 신호상태로 변경시키면 하기 코드 실행.
        hdc = GetDC(hWnd);
        for (i=0; i<=30++i)
        {
            wsprintf(str, "%d Line is %d", i, buf[i]);
            TextOut(hdc, 10, i*20, str, lstrlen(str));
        }
        ReleaseDC(hWnd, hdc);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        TextOut(hdc, 100100, Mes, strlen(Mes));
        EndPaint(hWnd, &ps);
        return 0;
    case WM_DESTROY:
        CloseHandle(hEvent);
        PostQuitMessage(0);
        return 0;
    }
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

WM_CREATE에서 최초 비신호상태인 자동 리셋 이벤트를 만들었고 사용자가 마우스 왼쪽 버튼을 클릭하면 별도의 스레드에서 DB를

읽기 시작한다. 그리고 DB 항목 중 30개를 읽으면 SetEvent를 통해서 hEvent를 신호 상태로 변경하여 메인 스레드에서 DB 내용을

출력하도록 한다.

WaitingForSingleObject는 hEvent가 신호 상태가 될 때까지 대기하다가 신호 상태가 되면 하기 코드를 실행시켜준다.

 

별도의 스레드에서는 SetEvent 후 자신이 맡은 작업을 백그라운드에서 혼자 진행하면 되고 메인 스레드는 자신의 다른 작업을 계속

이어서 하면 된다.

 

그리고 대기 함수인 WaitingForSingleObject 함수는 일단 대기가 종료되면 다음 사용을 위해 hEvent를 다시 비신호 상태로 변경시킨다.

 

 

+ Recent posts