데이터베이스실험실 석사 2학기 조정희 jjh1977@dblab.hannam.ac.kr TCP/IP Socket Programming… 제 19장 윈도우 기반의 쓰레드 동기화 데이터베이스실험실 석사 2학기 조정희 jjh1977@dblab.hannam.ac.kr
목차 쓰레드 동기화 기법의 분류 CRITICAL_SECTION Mutex(Mutual Exclusion) Semaphore Event
쓰레드 동기화 기법의 분류 [1] 프로세스 실행의 두 가지 모드 유저 모드란? 커널 모드란? 유저 모드(User Mode)와 커널 모드(Kernel Mode) 시스템의 안정성을 보장하기 위해서 제공 유저 모드란? 일반적인 프로그램의 실행 모드 시스템 리소스로의 접근이 허용되지 않음 유저 모드 동기화 기법 CRITICAL_SECTION 오브젝트의 사용 커널 모드란? 시스템 리소스로의 접근을 위한 프로세스의 실행 모드 커널 오브젝트를 통한 시스템 리소스(File, Thread 등등)의 접근 시 유저모드에서 커널 모드로의 변환이 발생 커널 모드 동기화 기법 Event, Semaphore, Mutex 커널 오브젝트의 사용
쓰레드 동기화 기법의 분류 [2] 두 가지 모드 동기화의 장단점 유저 모드 동기화의 장단점 커널 모드 동기화의 장단점 장점 : 프로그래밍 하기 수월하며 속도가 빠름 단점 : 커널 모드 동기화 기법에 비해서 제한된 기능을 가짐 커널 모드 동기화의 장단점 장점 : Deadlock 문제를 막을 수 있음(단, 해결책이 되는 것음 아님) 둘 이상의 프로세스 내에 존재하는 쓰레드 간의 동기화 가능 단점 : 실행 속도의 저하가 발생
CRITICAL_SECTION [1] 유저모드 동기화 기법 동기화 관련 함수 void InitializeCriticalSection( LPCRITICAL_SECTION lpCriticalSection); lpCriticalSection : 초기화할 CRITICAL_SECTION 변수의 포인터를 전달 void EnterCriticalSection( LPCRITICAL_SECTION lpCriticalSection); lpCriticalSection : 소유하고자 하는 CS의 포인터를 전달 만약 다른 쓰레드에 의해 소유된 상태라면 대기 상태로 들어감 이전에 소유했던 쓰레드가 임계 영역을 빠져 나오면서 CS를 반환하게 되면 대기 상태의 쓰레드는 CS를 얻게 됨과 동시에 임계 영역에 들어감 void LeaveCriticalSection( LPCRITICAL_SECTION lpCriticalSection); lpCriticalSection : 반환하고자 하는 CS의 포인터를 전달 void DeleteCriticalSection( LPCRITICAL_SECTION lpCriticalSection); lpCriticalSection : 소멸하고자 하는 CS의 포인터를 전달
CRITICAL_SECTION [2] 동기화 예제 int sum=0; int sum1[]={1, 5}; CRITICAL_SECTION cs; int main(int argc, char **argv) { HANDLE hThread1, hThread2; DWORD dwThreadID1, dwThreadID2; InitializeCriticalSection(&cs); hThread1 = (HANDLE)_beginthreadex(NULL, 0, ThreadSummation, (void*)sum1, 0, (unsigned *)&dwThreadID1); hThread2 = (HANDLE)_beginthreadex(NULL, 0, ThreadSummation, (void*)sum2, 0, (unsigned *)&dwThreadID2); if(hThread1==0 || hThread2==0) ErrorHandling("쓰레드 생성 오류"); if(WaitForSingleObject(hThread1, INFINITE)==WAIT_FAILED) ErrorHandling("쓰레드 wait 오류"); if(WaitForSingleObject(hThread2, INFINITE)==WAIT_FAILED) printf("main함수 종료, sum = %d \n", sum); DeleteCriticalSection(&cs); return 0; } DWORD WINAPI ThreadSummation(void *arg) { int start=((int*)arg)[0]; int end=((int*)arg)[1]; for( ; start<=end; start++) { EnterCriticalSection(&cs); sum+=start; LeaveCriticalSection(&cs); } return 0; void ErrorHandling(char *message) fputs(message, stderr); fputc('\n', stderr); exit(1);
CRITICAL_SECTION [3] 예제 실행 결과
커널 오브젝트의 상태 Mutex 기준 Semaphore 기준 signaled 상태 – 소유가 가능한 상태 non-signaled 상태 – 이미 소유되어진 상태 Semaphore 기준 signaled 상태 – 세마포어 카운트가 0이 아닌 경우 Non-signaled 상태 – 세마포어 카운트가 0인 경우
Mutex [1] Mutex 생성 함수 Mutex 소유 함수 WaitForSingleObject 함수 HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, // SD BOOL bInitialOwner, // initial owner(false for signaled) LPCTSTR lpName // object name ); lpMutexAttributes : 보안 관련 특성 전달(NULL) bInitialOwner : TRUE가 전달되는 경우는 생성된 Mutex의 소유자가 함수를 호출한 쓰레드를 초기화 FALSE가 전달되는 경우 Mutex의 소유자가 존재하지 않음 – Mutex는 signaled 상태 lpName : 생성되는 Mutex에 이름을 줄 경우 사용되는 인자(NULL – 이름 없는 Mutex를 생성)
Mutex [2] Mutex 반환 함수 Mutex 소멸 함수 BOOL ReleaseMutex( HANDLE hMutex // non-signaled -> signaled ); hMutex : 반환하고자 하는 Mutex를 인자로 전달 실제로는 Mutex의 상태를 non-signaled에서 signaled 상태로 바꾸는 함수 BOOL CloseHandle( HANDLE hObject); // handle to object hObject : 소멸하고자 하는 커널 오브젝트의 핸들을 전달
Mutex [3] Mutex의 동작 Signaled Non-Signaled Mutex 생성 상태 전환 A-1. WaitForSingleObject 호출 후 진입 A-2. ReleaseMutex 호출 후 탈출 B-1. WaitForSingleObject 호출 후 대기 임계 영역
Mutex [4] Mutex 예제 int number=0; HANDLE hMutex; int main(int argc, char **argv) { HANDLE hThread1, hThread2; DWORD dwThreadID1, dwThreadID2; hMutex = CreateMutex(NULL, FALSE, NULL); if(hMutex==NULL){ puts("뮤텍스 오브젝트 생성 실패"); exit(1); } hThread1 = (HANDLE)_beginthreadex(NULL, 0, ThreadIncrement, (void*)thread1, 0, (unsigned *)&dwThreadID1); hThread2 = (HANDLE)_beginthreadex(NULL, 0, ThreadIncrement, (void*)thread2, 0, (unsigned *)&dwThreadID2); if(hThread1==0 || hThread2==0) { puts("쓰레드 생성 오류"); if(WaitForSingleObject(hThread1, INFINITE)==WAIT_FAILED) ErrorHandling("쓰레드 wait 오류"); if(WaitForSingleObject(hThread2, INFINITE)==WAIT_FAILED) printf("최종 number : %d \n", number); CloseHandle(hMutex); //Mutex 오브젝트 소멸 return 0; } DWORD WINAPI ThreadIncrement(void *arg) { int i; for(i=0; i<5; i++) { WaitForSingleObject(hMutex, INFINITE); //Mutex를 얻는다. Sleep(100); number++; printf("실행 : %s, number : %d \n", (char*)arg, number); ReleaseMutex(hMutex);// Mutex를 반환한다. void ErrorHandling(char *message) fputs(message, stderr); fputc('\n', stderr); exit(1);
Mutex [5] 예제 실행 결과
Semaphore [1] Semaphore의 특징 Semaphore 생성 함수 세마포어가 0이 되어야 세마포어 오브젝트가 non-signaled 상태가 됨 Semaphore 생성 함수 HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // SD LONG lInitialCount, // initial count LONG lMaximumCount, // maximum count LPCTSTR lpName // object name ); lpSemaphoreAttributes : Mutex와 마찬가지로 Null lInitialCount : Semaphore의 생성 시 초기 값(count 값)을 설정 0이상이고 lMaximumCount에 전달되는 값보다 같거나 적어야 함 0인 경우 non-signaled 상태이고 0보다 큰 경우 signaled 상태 1인경우 Semaphore 얻을 수 있는 상태이고 0인 경우 Semaphore가 누군가에게 소유된 상태 lMaximumCount : 최대 카운트 값 결정 1을 최대값으로 설정하면 바이너리 Semaphore lpName : 생성되는 Semaphore에 이름을 줄 경우 사용되는 인자
Semaphore [2] Semaphore 반환 함수 BOOL ReleaseSemaphore( HANDLE hSemaphore, // handle to semaphore LONG lReleaseCount, // count increment amount LPLONG lpPreviousCount // previous count ); hSemaphore : 반환하고자 하는 Semaphore의 핸들을 전달 lReleaseCount : 반환한다는 것은 카운트의 증가를 의미 일반적으로 1을 전달 최대 카운트(Semaphore 생성 시 결정)를 넘겨서 증가시킬 것을 요구하는 경우 카운트 변경없이 FALSE만 리턴 lpPreviousCount : ReleaseSemaphore 함수 호출로 변경되기 전의 카운트 값을 저장할 변수의 포인터 전달 필요 없다면 NULL 포인터 전달
Semaphore [3] Semaphore 예제 hSem=CreateSemaphore(NULL, 0, 1, NULL); // 바이너리 세마포어 생성. hSem2=CreateSemaphore(NULL, 0, 1, NULL); DWORD WINAPI ThreadSend(void * arg) { int i; for(i=0; i<4; i++){ number++; printf("실행 : %s, number : %d \n", (char*)arg, number); ReleaseSemaphore(hSem, 1, NULL); // hSem 세마포어 1 증가! WaitForSingleObject(hSem2, INFINITE); // hSem2 세마포어 1 감소! } return 0; DWORD WINAPI ThreadRecv(void * arg) for(i=0; i<2; i++){ WaitForSingleObject(hSem, INFINITE); // hSem 세마포어 1 감소! number--; printf("실행 : %s, number : %d \n", (char*)arg, number); ReleaseSemaphore(hSem2, 1, NULL); // hSem2 세마포어 1 증가!
Semaphore [4] 예제 실행 결과
Event [1] Event의 특징 Event 생성 함수 auto & manual-reset 모드의 커널 오브젝트의 생성이 가능 Event 생성 함수 HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, // SD BOOL bManualReset, // reset type, TRUE for manual-reset BOOL bInitialState, // initial state, TRUE for signaled state LPCTSTR lpName // object name ); lpEvntArrtibutes : NULL 포인터 전달 bManualReset : CreateEvent 함수의 가장 중요한 전달 인자 manual-reset 모드 Event를 생성하느냐 auto-reset Event를 생성하느냐를 결정 TRUE 전달할 경우 manual-reset 모드의 Event 생성 – Event가 signaled 상태에 있을 경우 ResetEvent 함수를 호출하지 않는 이상 non-signaled 상태로 돌아가지 않음을 의미 FALSE 전달할 경우 auto-reset 모드 Event 생성 – WaitForSingleObject 함수 호출이 성공적으로 끝나는 경우 알아서 non-signaled로 변경을 의미 bInitialState : TRUE인 경우 signaled 상태의 Event 생성, FALSE인 경우 non-signaled Event 생성 lpName : 생성되는 Event에 이름을 줄 경우 사용하는 인자
Event [2] manual-reset 모드 Event 동기화 원리 대기 상태의 쓰레드 Manual Reset Event Main thread Non-signaled thread 1. ResetEvent 2. Signaled 상태로… 3. 모두 깨운다! Manual Reset Event 대기 상태의 쓰레드
Event [3] Event 예제 DWORD WINAPI NumberOfA(void *arg) { int i; int count=0; WaitForSingleObject(hEvent, INFINITE); //Event를 얻는다. for(i=0; String[i]!=0; i++) { if(String[i]=='A') count++; } printf("A 문자의 수 : %d\n", count); return 0; DWORD WINAPI NumberOfOthers(void *arg) if(String[i]!='A') printf("A 이외의 문자 수 : %d\n", count-1); void ErrorHandling(char *message) fputs(message, stderr); fputc('\n', stderr); exit(1); hThread1 = (HANDLE)_beginthreadex(NULL, 0, NumberOfA, NULL, 0, (unsigned *)&dwThreadID1); hThread2 = (HANDLE)_beginthreadex(NULL, 0, NumberOfOthers, NULL, 0, (unsigned *)&dwThreadID2); if(hThread1==0 || hThread2==0) { puts("쓰레드 생성 오류"); exit(1); } fputs("문자열을 입력 하세요 : ", stdout); fgets(String, 30, stdin); SetEvent(hEvent); // Event 오브젝트를 signaled 상태로 변경 if(WaitForSingleObject(hThread1, INFINITE)==WAIT_FAILED) ErrorHandling("쓰레드 wait 오류"); if(WaitForSingleObject(hThread2, INFINITE)==WAIT_FAILED) CloseHandle(hEvent); //Event 오브젝트 소멸 return 0;
Event [4] Event 예제 실행 결과
참고문헌 “TCP/IP 소켓 프로그래밍”, 윤성우 저
Q & A