크리티컬 섹션

 - 유저레벨의 동기화 방법 중, 유일하게 커널 객체를 사용하지 않음.
 - 내부 구조가 단순하여 동기화 처리에 대한 속도가 빠르다.
 - 동일한 프로세스내에서만 사용.
 - 커널 객체를 사용하지 않기 때문에 핸들을 사용하지 않고, CRITICAL_SECTION라는 타입을
    정의하여 사용.
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]의 방법으로 신호상태가 된 동기화객체의 핸들을
구할수 있다.
 
뮤텍스
 - 최초에 Signaled 상태로 생성되어지며, WaitForSingleObject()와 같은 대기 함수를 호출함으로
   써 NonSignaled 상태가 된다.
 - 만약 A라는 스레드가 뮤텍스를 소유하고 있고, B라는 스레드가 뮤텍스를 사용하기 위하여 대기

   하고 있을 때, A라는 스레드가 잘못된 연산을 수행하거나 강제 종료되어서 소유하고 있던 뮤텍스
   를 반환하지 않았을 때에 B라는 스레드는 뮤텍스를 얻기 위해 무한정 기다릴까?
   ==> 만약 크리티컬 섹션을 사용하였다면 B라는 스레드는 무한정 기다릴게 될 것이다.
         하지만, 뮤텍스의 경우는 자신이 소유한 스레드가 누군지 기억하고 있다. 그리고
         Windows 운영체제에서 뮤텍스를 반환하지 않는 상태에서 스레드가 종료될 경우
         그 뮤텍스를 강제적으로 Signaled상태로 해 준다.
 - 만약 같은 스레드가 중복으로 뮤텍스를 호출할 경우 데드락이 발생할까?
   ==> 발생하지 않는다. 왜나면 같은 스레드가 중복으로 뮤텍스를 호출할 경우는 내부 Count만
         증가시키고, 진입은 허용한다. 그리고 나중에 내부 Count가 '0'으로 될 때 Signaled 상태로
         해 준다.(이 부분은 크리티컬 섹션도 동일한 개념)

 

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

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함수로 수동리셋을 한다.
 
실험
 InterlockCriticalSectionMutexNot sync
Real Time09:23409:9531:59:73409:000
User Time18:40618:93735:18717:859
Kernel Time00:01500:3751:28:60800:000

 

'Tech: > C·C++' 카테고리의 다른 글

[펌] Win32 API FAQ  (1) 2008.06.26
VC++2005 배포  (0) 2008.06.26
ISAPI Filter 예제 2 (MFC + WIN32 API)  (0) 2008.06.26
ISAPI Filter 예제 1 (MFC)  (0) 2008.06.26
ISAPI Filter에 대해... (1)  (0) 2008.06.26


Posted by 떼르미
,


자바스크립트를 허용해주세요!
Please Enable JavaScript![ Enable JavaScript ]