본문 바로가기

3.구현/VC++

Win32 쓰레드에서 동기화 방법

아래 내용은 인터넷에서 가져온것인데... 원 저작자를 모르겠다.
여러 동기화 방법에 대해서 이야기한 것이면 문제에 대해서 다루고 있지 않다.
동기화 방법으로 Interlock, CriticalSection, Mutex, Semaphore 등이 있다.

크리티컬 섹션

void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

크리티컬 섹션을 초기화한다. 여기 들어가는 인자는 여러개의 스레드에 참조가 되야 하므로 주로 전역에서 쓰인다.

void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

생성된 크리티컬 섹션을 삭제한다.
CRITICAL_SECTION 구조체는 구체적으로 사용할 일이 없다. 그냥 주소를 넘겨주기만 하면 된다.

void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

이 사이에서 공유 자원을 안전하게 액세스한다.

void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

동기화 대기 함수

DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMiliseconds);

hHandle는 동기화 객체를 나타내고 dwMiliseconds는 기다리는 시간을 정한다. 역시 INFINITE로 지정하면 무한대로 기다린다.
반환값은 세가지 종류이다
성공을 하였을때 WAIT_OBJECT_0 hHandle객체가 신호상태가 된경우
WAIT_TIMER 설정된 시간을 경과하였을 경우 WAIT_ABANDONED 포기된 경우.

DWORD WaitForMultipleObject(DWORD nCount, CONST HANDLE *lpHandles, BOOL fWaitAll, DWORD dwMiliseconds);

위의 WaitForSingleObject함수가 하나의 객체에 동기화를 기다리는데 비해 이 함수는 복수개의 동기화 객체를 대기할 수 있다.
동기화 객체의 핸들 배열을 만든후 lpHandles인수로 배열의 포인터를 전달해주면 nCount로 배열의 갯수를 넘겨준다.
fWaitAll이 TRUE이면 모든 동기화 객체가 신호상태가 될 때까지 대기하면 FALSE이면 그중하나라도 신호상태가 되면 대기상태를 종료한다.
리턴값의 의미가 조금 다르다. WAIT_TIMEOUT은 같고 bWaitAll이 TRUE이면 WAIT_OBJECT_0이 리턴되면 모든 동기화 객체가 신호상태이라는
말이고 FALSE이면 lpHandles배열에서 신호상태가 된 동기화 객체의 인덱스를 넘겨준다. 이경우
lpHandles[리턴값 - WAIT_OBJECT_0]의 방법으로 신호상태가 된 동기화객체의 핸들을 구할수 있다.

뮤텍스

크리티컬 섹션에 비해서 느리다
크리티컬 섹션의 경우 구조체의 값을 통해 잠그기를 허용하는데 비해 뮤텍스는 객체를 생성하기 때문이다.

HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName);

lpMutexAttributes는 보안속성으로 보통 NULL로 지정한다.
bInitialOwner은 뮤텍스 생성과 동시에 소유할것인지 지정하는데 TRUE이면 이 스레드가 바로 뮤텍스를 소유하면서 다른 스레드는 소유할수 없다.
lpName는 뮤텍스의 이름이다. NULL설정가능.
반환값은 뮤텍스의 핸들이다.

HANDLE OpenMutex(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName);

뮤텍스를 연다. 프로세스 ID와 마찬가지로 뮤텍스의 이름은 전역적으로 유일하다.

BOOL ReleaseMutex(HANDLE hMutex);

해당 스레드의 뮤텍스 소유를 해제하여 다른 스레드가 가질수 있도록 해준다.

HRESULT CloseHandle(HANDLE hHandle);

모든 커널 객체와 마찬가지로 생성된 뮤텍스를 파괴할때 사용한다.
반환값은 S_OK면 성공 그외의 값은 에러이다.

포기된 뮤택스

만약 뮤텍스를 소유하고 있는 스레드가 ExitThread나 TerminateThread로 비정상적으로 종료시켰을 경우 강제로 뮤텍스를
신호상태로 만들어준다. 그러므로 대기중인 다른 스레드에서 뮤텍스를 가지게 되는데 WaitForSingleObject함수의 리턴값으로
WAIT_ABANDONED값을 전달받음으로 이 뮤텍스가 정상적인 방법으로 신호상태가 된 것이 아니라 포기된 상태임을 알 수 있다.

중복소유

뮤텍스를 여러번 겹쳐서 사용했을 경우 데드락과 같은 상태에 빠질수도 있을것이다. 하지만 중복으로 소유하기위해
Wait~Objet함수를 호출하여 기다릴때 뮤텍스를 여러번에 겹쳐서 소유하는게 아니라 한번의 뮤텍스를 소유하고 소유횟수
(카운트)를 증가시킨다. 단, 다시 뮤텍스를 신호상태로 만들기 위해서는 ReleaseMutex를 카운트많큼 호출해주어야한다.
중복 소유에 대해서는 크리티컬 섹션과 같다.

공유자원의 해야 할 일
WaitForSingleObject와 같은 대기상태에서 무한정으로 기다릴게 아니라 dwMiliseconds에 0을 넣어주고 반환값 WAIT_TIMEOUT을
체크해주면 if문등을 통해 쉽게 공유자원외의 일을 할 수 있다.

세마포어

세마포어와 뮤텍스는 유사한 동기화 객체이다. 뮤텍스는 하나의 공유자원을 보호하는데 비해
세마포어는 일정 개수를 가지는 자원을 보호할수 있다. 여기서 자원이라함은 윈도우, 프로세서,
스레드와 같은 소프트웨어적인거나 어떤 권한과 같은 무형적인것도 포함된다.

HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName);

lMaximumCount는 최대 사용 개수 lInitialCount에 초기값을 지정. 아주 특별한 경우외에는 이 두값이 같다. 세마포어는 뮤텍스와 같이
이름을 가질 수 있고 이름을 알고 있는 프로세스는 언제든지 OpenSemaphore로 핸들을 구할 수 있다. 역시 파괴할때는 CloseHanle함수를 사용한다.

HANDLE OpenSemaphore(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName);

뮤텍스 부분과 같다.

BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG lReelaseCount, LPLONG lpPreviousCount);

lReleaseCount로 사용한 자원의 개수를 알려줌. lpPreviousCount는 세마포어 이전 카운트를 리턴받기 위한 참조 인수이다. NULL가능하다.

이벤트

위의 동기화객체들이 공유자원을 보호하기 위해 사용되는 데 비해 이벤트는 스레드의 작업순서나 시기를 조정하기 위해 사용된다.

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

bManualReset은 이벤트가 수동 리셋(스레드가 비신호상태로 만들어줄 때까지 신호상태를 유지)인지
자동 리셋(대기 상태가 종료되면 자동으로 비신호상태가 된다.)인지를 결정한다. TRUE이면 수동이다.
bInitialState가 TRUE이면 자동으로 신호상태로 들어가 이벤트를 기다리는 스레드가 곧바로 실행할수 있게 한다.

HANDLE OpenEvent(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName);

다른 부분과 같다.

BOOL SetEvent(HANDLE hEvent);

다른 동기화 객체와는 다르게 사용자 임으로 신호상태와 비신호상태를 설정할 수 있다. 위의 함수는 신호상태로 만들어 준다.

BOOL ResetEvent(HANDLE hEvent);

비신호 상태로 만든다.
일반적으로 자동리셋을 사용하는데 이벤트를 발생시켜 대기 상태를 풀 때 자동으로 비신호 상태로 만드는 것이다.
하지만 여러개의 스레드를 위해서 이벤트를 사용한다면 문제가 될수도 있다. 그러므로 수동리셋으로 이벤트를 생성후
ResetEvent함수로 수동리셋을 한다.

각 동기화간 성능 비교

Interlock CriticalSection? Mutex Not sync
Real Time 09:234 09:953 1:59:734 09:000
User Time 18:406 18:937 35:187 17:859
Kernel Time 00:015 00:375 1:28:608 00:000
반응형