생산자/소비자 모델
생산자/소비자 모델을 풀어 말하면 실행 순서에 있어서의 동기화라고 할 수 있다.
가령 빵을 만드는 생산자가 빵을 구워서 테이블에 두면 소비자는 빵을 사먹을 수 있다. 이 순서가 바뀌어 소비자가 비어있는 테이블에 빵을 찾게 되고 그 뒤 생산자가 빵을 놓게 된다면 적절하지 못한 순서가 된 것이다. 실행 순서가 동기화 되지 못한것이다.
실행 순서에 있어서 동기화를 생산자/소비자 모델이라 부르는 이유이다.
이를 바탕으로 한 쓰레드가 문자를 입력 받고 출력하는 역할을 모두 실행한다면 출력속도가 입력속도를 따라가지 못하는 경우가 발생하게 되고 어느 순간 문자열이 손실될 것입니다.
위와 같은 현상을 방지하고자 두개의 쓰레드를 이용합니다. A 쓰레드는 입력만 받고 B 쓰레드는 출력만 하는 것이죠.
A와 B 쓰레드 사이에 버퍼를 두고 입출력 속도에 상관없이 독립적으로 실행하게 합니다.
Event 동기화
뮤텍스, 세마포어와 마찬가지로 CreateEvent()로 이벤트 오브젝트를 생성합니다. 마찬가지로 소멸시에 CloseHandle()을 이용하면 됩니다.
쓰레드나 프로세스 커널 오브젝트의 경우 Non-Signaled 상태로 생성되고 종료될 경우 Signaled 상태로 자동으로 변경됩니다. 하지만 이벤트 오브젝트는 자동으로 Signaled 상태가 되지 않습니다.
Non-Signaled 상태의 이벤트 오브젝트가 WaitForSingleObject()가 호출되면 Blocking 상태가 됩니다. 하지만 Signaled 상태가 되면 WaitForSingleObject() 함수를 반환합니다.
이벤트 오브젝트가 Signaled 상태가 되어 Blocking 상태에서 빠져나와 Signaled 상태이면 Manual-Rest Mode라고 하고 Non-Signaled 이면 Auto-Rest Mode라고 합니다.
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 | #include <stdio.h> #include <tchar.h> #include <Windows.h> #include <process.h> TCHAR string[100]; HANDLE hEvent; unsigned int WINAPI OutputThread(LPVOID lpParam) { WaitForSingleObject(hEvent, INFINITE); _tprintf(_T("Output string: %s \n"), string); return 0; } int _tmain(int argc, TCHAR* argv[]) { HANDLE hThread; DWORD dwThreadID; hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (hEvent == NULL) { printf("Event Ojbect creation error \n"); return -1; } hThread = (HANDLE)_beginthreadex(NULL, 0, OutputThread, NULL, 0, (unsigned*)&dwThreadID); if (hThread == NULL) { printf("Thread creation error \n"); return -1; } printf("Insert string: "); _fgetts(string, 30, stdin); SetEvent(hEvent); WaitForSingleObject(hThread, INFINITE); CloseHandle(hEvent); CloseHandle(hThread); return 0; } | cs |
main 함수에서 _beginthreadex()를 통해서 OutputThread 쓰레드를 실행시킨다. 하지만 11라인에서 WaitForSingleObject()를 통해서 Signaled 상태가 될 때까지 기다린다.
23라인에서 CreateEvent()의 2번째 인자 값을 보면 TRUE로 되어잇는데 이것은 Manual-Rest Mode를 뜻하고 3번째 인자 값은 FALSE로 non-signaled 상태를 말한다.
40라인에서 문자열을 입력한 뒤에 42라인 SetEvent()에서 Event를 Signaled 상태로 변경한다.
이번에는 쓰레드를 하나 더 추가해서 해당 길이를 출력하는 것을 추가로 만들어 보자.
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 61 | #include <stdio.h> #include <tchar.h> #include <Windows.h> #include <process.h> TCHAR string[100]; HANDLE hEvent; unsigned int WINAPI OutputThread(LPVOID lpParam) { WaitForSingleObject(hEvent, INFINITE); _tprintf(_T("Output string: %s \n"), string); return 0; } unsigned int WINAPI CountThread(LPVOID lpParam) { WaitForSingleObject(hEvent, INFINITE); _tprintf(_T("Length: %d \n"), _tcslen(string) - 1); return 0; } int _tmain(int argc, TCHAR* argv[]) { HANDLE hThread[2]; DWORD dwThreadID[2]; hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (hEvent == NULL) { printf("Event Ojbect creation error \n"); return -1; } hThread[0] = (HANDLE)_beginthreadex(NULL, 0, OutputThread, NULL, 0, (unsigned*)&dwThreadID[0]); hThread[1] = (HANDLE)_beginthreadex(NULL, 0, CountThread, NULL, 0, (unsigned*)&dwThreadID[1]); if (hThread[0] == NULL || hThread[1] == NULL) { printf("Thread creation error \n"); return -1; } printf("Insert string: "); _fgetts(string, 30, stdin); SetEvent(hEvent); WaitForMultipleObjects(2, hThread, TRUE, INFINITE); CloseHandle(hEvent); CloseHandle(hThread[0]); CloseHandle(hThread[1]); return 0; } | cs |
32라인을 보면 CreateEvent()의 2번째 인자 값이 FALSE로 되어있다. 이것은 Auto-Rest Mode로 한다는 말이다. Auto-Rest Mode는 자동으로 Non-Signaled 상태가 되므로 첫번째 쓰레드는 정상적으로 작동하지만 두번째 쓰레드는 WaitForSingleObject()에서 빠져나오지 못하게 된다.
출력결과는 아래와 같다.
32라인의 CreateEvent() 2번째 인자 값을 TRUE로 변경해 보자.
위 2개의 그림 중 첫번째 그림이 생각하는 것과 동일한 결과이다. 하지만 계속해서 정상적인 출력을 볼 수 없다.
순서대로 실행을 했다면 말이다. 하지만 52라인에서 SetEvent()를 통해서 Signaled로 바꾸면서 두개의 쓰레드는 모두 Signaled로 바뀌게 되고 모두 실행을 하게 되는 것이다.
이벤트 + 뮤텍스 동기화
위 이벤트 동기화 예제에서 실행이 정상적으로 이루어지지 않는다는 것을 봤다. 이를 해결하기 위해서 이벤트와 뮤텍스를 모두 사용해야 한다.
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 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | #include <stdio.h> #include <tchar.h> #include <Windows.h> #include <process.h> typedef struct _SynchString { TCHAR string[100]; HANDLE hEvent; HANDLE hMutex; } SynchString; SynchString gSynString; unsigned int WINAPI OutputThread(LPVOID lpParam) { WaitForSingleObject(gSynString.hEvent, INFINITE); WaitForSingleObject(gSynString.hMutex, INFINITE); _tprintf(_T("Output string: %s \n"), gSynString.string); ReleaseMutex(gSynString.hMutex); return 0; } unsigned int WINAPI CountThread(LPVOID lpParam) { WaitForSingleObject(gSynString.hEvent, INFINITE); WaitForSingleObject(gSynString.hMutex, INFINITE); _tprintf(_T("Length: %d \n"), _tcslen(gSynString.string) - 1); ReleaseMutex(gSynString.hMutex); return 0; } int _tmain(int argc, TCHAR* argv[]) { HANDLE hThread[2]; DWORD dwThreadID[2]; gSynString.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); gSynString.hMutex = CreateMutex(NULL, FALSE, NULL); if (gSynString.hEvent == NULL || gSynString.hMutex == NULL) { printf("kernel object creation error \n"); return -1; } hThread[0] = (HANDLE)_beginthreadex(NULL, 0, OutputThread, NULL, 0, (unsigned*)&dwThreadID[0]); hThread[1] = (HANDLE)_beginthreadex(NULL, 0, CountThread, NULL, 0, (unsigned*)&dwThreadID[1]); if (hThread[0] == NULL || hThread[1] == NULL) { printf("Thread creation error \n"); return -1; } printf("Insert string: "); _fgetts(gSynString.string, 30, stdin); SetEvent(gSynString.hEvent); WaitForMultipleObjects(2, hThread, TRUE, INFINITE); CloseHandle(gSynString.hEvent); CloseHandle(gSynString.hMutex); CloseHandle(hThread[0]); CloseHandle(hThread[1]); return 0; } | cs |
책의 출력결과를 보면 마지막에 문자열에 대한 길이가 출력이되는데 아무리 해도 문자열 길이가 중간에 출력이 된다.
출판사에서 제공하는 소스코드를 사용해도 마찬가지.
처음 출력에는 예상대로 나왔지만 몇 차례 계속 진행을 해보니 그냥 Event 동기화와 다를게 없었다.
이 부분은 나중에 더 공부해서 알아보도록 해야겠다.
Waitable Timer 동기화
정해진 시간이 지나면 자동으로 signaled 상태가 되도록 하는 것을 Waitable Timer 동기화라 한다.
Manual-Rest Timer
CreateWaitableTimer()를 이용해 타이머 오브젝트를 생성합니다. CreateEvent()와 비교했을 때 Signaled나 Non-Signaled 상태를 설정하는 인자 값이 존재하지 않습니다.
Timer 자체가 일정 시간이 되면 자동으로 Signaled 상태가 되기 때문입니다.
따라서 타이머 오브젝트를 생성하면 Non-Signaled 상태가 됩니다.
CreateWaitableTimer()로 타이머 오브젝트를 생성하고 나면 SetWiatableTimer()로 시간을 설정해 주어야 합니다.
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 | #define _WIN32_WINNT 0x0400 #include <stdio.h> #include <tchar.h> #include <Windows.h> int _tmain(int argc, TCHAR* argv[]) { HANDLE hTimer = NULL; LARGE_INTEGER liDueTime; liDueTime.QuadPart = -100000000; hTimer = CreateWaitableTimer(NULL, FALSE, _T("WaitableTimer")); if (!hTimer) { printf("CreateWaitableTimer failed %d \n", GetLastError()); return -1; } printf("Waiting for 10 seconds... \n"); SetWaitableTimer(hTimer, &liDueTime, 0, NULL, NULL, FALSE); WaitForSingleObject(hTimer, INFINITE); printf("Timer was signaled \n"); MessageBeep(MB_ICONEXCLAMATION); return 0; } | cs |
10초 후에 MessageBeep()가 실행되는 소스코드 입니다. WaitForSingleObject()에서 Timer가 Signaled 상태가 될 때까지 기다리다가 Signaled 상태가 되면 빠져나와 MessageBeep()를 실행시키게 됩니다.
Periodic Timer
주기적으로 작동하는 타이머를 말합니다.
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 | #define _WIN32_WINNT 0x0400 #include <stdio.h> #include <tchar.h> #include <Windows.h> int _tmain(int argc, TCHAR* argv[]) { HANDLE hTimer = NULL; LARGE_INTEGER liDueTime; liDueTime.QuadPart = -100000000; hTimer = CreateWaitableTimer(NULL, FALSE, _T("WaitableTimer")); if (!hTimer) { printf("CreateWaitableTimer failed %d \n", GetLastError()); return -1; } printf("Waiting for 10 seconds... \n"); SetWaitableTimer(hTimer, &liDueTime, 5000, NULL, NULL, FALSE); while (1) { WaitForSingleObject(hTimer, INFINITE); printf("Timer was signaled \n"); MessageBeep(MB_ICONEXCLAMATION); } return 0; } | cs |
중간에 타이머를 해제하고자 하는 경우에 CancelWaitableTimer()를 이용하면 된다. 단 타이머를 소멸시키거나 할당된 자원을 반환하는 함수가 아니다.
소멸을 하고자 할 때는 CloseHandle()을 이용해야 한다.
'High Level Technique > Window System' 카테고리의 다른 글
캐쉬와 가상메모리 (0) | 2017.01.05 |
---|---|
쓰레드 풀 (Thread Pool) (0) | 2017.01.04 |
쓰레드 동기화 (0) | 2017.01.03 |
Thread 생성과 소멸 (0) | 2017.01.02 |
쓰레드 (Thread) (0) | 2017.01.02 |