뮤텍스 기반의 동기화 -----------------------------------------------------------------------------------------

크리티컬섹션 기반의 동기화방식과 비슷하다.

위와 같은 함수를 사용하며 핸들을 반환하는 것을 보면 알겠지만 핸들을 반환하며 초기화 함수가 필요없다.


앞서 커널오브젝트의 상태에 대해서 설명한 적이 있는데. 뮤택스 또한 커널오브젝트를 생성한다. 뮤텍스의 커널 오브젝트는 누군가가 열쇠를 취득했을때 Non-Signaled상태가 되고 열쇠가 반환되면 Signaled상태가 가 된다. 그리고 이러한 상태를 이용하여 동기화가 가능하다.

즉 커널오브젝트에서 설명했던 WaitForSingleObject함수를 임계영역 진입을 위한 뮤텍스 획득의 용도로 사용이 가능하게 된다.

뮤텍스도 커널오브젝트이며 핸들이 존재하므로 해제가 필요하며 이는 윈도우에서 함수로 존재한다.

BOOL ReleaseMutex ( HANDLE hMutex ) 함수를 통하여 해제가 가능하며 뮤텍스는 다시 Signaled 상태가 된다.

위의 설명의 이해를 돕기 위해서 아래와 같이 코드를 예제로 들어본다.

HANDLE hMutex; -> 이 핸들을 이요하여 뮤텍스를 생성한다고 본다.

LONG g_count = 0;

void IncreaseCount()

{

// EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

WaitForSingleObject (hMutex, INFINITE);

g_count++;

// LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

ReleaseMutex (hMutex) ;

}

식으로 이용이 가능하다

WaitForSingleObject함수는 위와 같은 방식 말고도 다양한 방식으로 이용이 가능한데 아래와 같은 함수를 만들어 호출하는 방식으로도 이용이 가능하다.

DWORD AcquireMutex( HANDLE hMutex )

{

return WaitForSingleObject (hMutex, INFINITE);

}

간혹 함수를 한번 호출한다는 이유로 이런 코드를 부정적으로 바라보는 경우가 있는데, 컴파일러의 최적화로 인해서 함수의 호출 문장을 함수 의 몸체로 대치시켜 버리는 경우가 생긴다. 즉 코드를 통해서 의미를 전달할 뿐이지 실제 작동은 그냥 함수를 적용시킬 때랑 동일하게 적용 된다.


세마포어 기반의 동기화 ---------------------------------------------------------------------------------------

"세마포어 중에서 단순화된 세마포어(바이너리 세마포어를 의미한다.)를 가르켜 뮤텍스라고 한다"

뮤텍스는 세마포어의 일종이라고 말할 수가 있다.

하지만 뮤텍스와 세마포어는 차이가 있는데. 그 차이는 카운트(Count) 기능이라고 할 수 있다. 세마포어는 카운트 기능이 존재하지만 뮤텍스에는 존재하지 않는다.

카운트 기능이란 임계영역에 접근 가능한 쓰레드의 개수를 조절하는 기능을 의미한다.

HANDLE CreateSemaphore ( 

LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,

LONG lInitialCount,

LONG lMaximumCount,

LPCTSTR lpName

);

LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,

-> 보안속성을 지정하기 위한 매개변수

LONG lInitialCount,

-> 세마포에서 가장 중요한 전달인자이다. 세마포어는 값을 지니는데 이 값을 기준으로 접근 가능한 쓰레드의 개수를 설정한다.

LONG lMaximumCount,

세마포어가 지닐수 있는 값의 최대 크기를 지정한다. 이 값이 1일 경우에는 뮤텍스와 동일한 기능을 하는 바이너리 세마포어가 구성된다. 기본적으로 lInitialCount인자 값보다 큰 값을 지녀야 한다.

LPCTSTR lpName

세마포어에 이름을 붙이기 위해서 사용한다. 커널 오브젝트가 이름을 지닐 때 어떠한 용도로 사용되는지 잠시 후에 소개한다.


이렇게 할당된 세마포어는 당연히 커널 오브젝트를 가지게 되며, 해제를 위해서는 다음의 함수를 사용해야 한다.

BOOL ReleaseSemaphore (

HANDLE hSemaphore,

LONG lReleaseCount,

LPLONG lpPreviousCount

);

HANDLE hSemaphore,

-> 반환하고자 하는 세마포어의 핸들을 인자로 전달한다.

LONG lReleaseCount,

-> 반환한다는 것은 lInitialCount 카운트의 증가를 의미한다. 이 전달인자를 통해서 증가시킬 값의 크기를 결정할수 있다. 만약 lMaximumCount를 넘겨서 증가시킨다면 카운트는 변경되지 않고 FALSE만 반환한다.

LPLONG lpPreviousCount 변경되기 전 세마포어 카운트값을 저장할 변수를 저장한다. 필요 없다면 NULL을 전달한다.


실제 세마포어의 이용


HANDLE hSemaphore; -> 이 핸들을 이요하여 세마포어를 생성한다고 본다.

LONG g_count = 0;

void IncreaseCount()

{

WaitForSingleObject (hSemaphore, INFINITE);

g_count++;

ReleaseMutex (hSemaphore, 1, NULL) ;

}


WaitForSingleObject 의 최대값? -------------------------------------------------------------------------------

WaitForSingleObject 함수는 최대 64개의 커널 오브젝트를 관찰할 수 있다.  이는 이 함수에 설정되어있는 매크로 값인 MAXIMUM_WAIT_OBJECT 값이 64로 제한되어 있기 때문이다.


이름 있는 뮤텍스기반의 프로세스 동기화 -----------------------------------------------------------------------

앞서 설명하기에 커널오브젝트는 운영체제에 종속적으로 표현되었다. 즉 뮤텍스란 프로세스에 속한 녀석이 아니기 때문에 뮤텍스에 이름을 붙이고 프로세스들이 이 이름을 알고만 있다면 모든 프로세스들의 쓰레드가 이 뮤텍스를 이용할 수가 있을 것이다.

이런 점에서 이름있는 뮤텍스와 없는 뮤텍스의 차이점이 발생한다. 이름있는 뮤텍스를 정리한다면.

서로다른 프로세스에 있는 두개의 쓰레드를 동기화가 가능하다.


하지만 위와 같이 전혀 관련이 없는 각 프로세서끼리는 핸들등의 상속또한 불가능하기 때문에 한쪽에서 생성된 뮤텍스에 접근이 불가능 하다. 이런 점을 위해서 뮤텍스에 이름을 붙이게 되는 것이다. 앞서 설명했던 이름 업는 뮤텍스와 같이 일단 CreateMutex 함수를 사용해야 한다.

CreateMutex (

LPSECURITY_ATTRIBUTES lpMutexAttributes,

BOOL bInitialOwner,

LPCTSTR lpName -> 이부분에 이름을 붙여줌으로 해서 다른 프로세스가 뮤텍스에 접근할 수 있게 된다.

)

뮤텍스에 접근을 하기 위해서는 OpenMutex라는 함수가 필요하다.

HANDLE OpenMutex (

DWORD dwDesiredAccess,

BOOL bInheritHandle,

LPCTSTR lpName -> 다른 프로세스에서 생성한 뮤텍스의 이름을 넣어줌으로서 다른 프로세스에 존재하는 뮤텍스를 오픈 할 수 있다.

);


 여기서 잠깐! WaitForSingleObject -----------------------------------------------------------------------------

 커널 오브젝트를 읽을때 그냥 넘어갔던 함수가 있었는데 여기서 다시 설명해야할 듯 하다. WaitForSingleObject함수가 존재하는데 이 함수는 핸들을 인자로 적용해서 커널 오브젝트의 상태를 확인하는데 사용되는 함수이다. 

 DWORD WaitForSingleObject {

HANDLE hHandle,         -> 커널 오브젝트의 핸들

DWORD dwMilliseconds -> 커널오브젝트가 종료될때까지 기다려주는 값 밀리세컨트단위를 사용한다.

}

이 함수가 반환하는 값은 다음과 같다.

 Priority 

 meaning 

 WAIT_OBJECT_0

 커널 오브젝트가 Signaled 상태가 되면 반환하는 값

 WAIT_TIMEOUT

 커널 오브젝트가 Signaled 상태가 되지 않고, dwMilliseconds 인자를 통해서 설정된 시간이 다 된 경우에 반환하는 값

 WAIT_ABANDONED

 소유 관계와 관련하여 함수가 정상적이지 못한 오류 발생에 의해서 반환되는 값

하나의 함수가 더있는데 이녀석은 상태를 확인하려고 하는 커널오브젝트가 둘 이상이고 이들의 핸들이 배열로 묶여있다면 활용하기 편리하다.

 DWORD WaitForMultipleObject {

DWORD nCount,         -> 배열에 저장되어있는핸들의 개수를 전달한다.

const HANDLE* lpHandles, -> 핸들을 저장하고 있는 배열의 주소 정보를 전달한다. 이 주소값을 시작으로 총 nCount개의 핸들을 관찰 대상으로 둔다.

BOOL bWaitAll            -> 관찰 대상이 모두 Signaled 상태가 되기를 기다리고자 하는지 TRUE전달시, 아니면 하나라도 Signaled상태가 되면 반환할 것인지(FALSE, 전달시)를 결정 짓는다.

DWORD dwMilliseconds -> 커널오브젝트가 종료될때까지 기다려주는 값 밀리세컨트단위를 사용한다.

}


뮤텍스의 소유와 WAIT_ABANDONED -----------------------------------------------------------------------

WaitForSingleObject 함수의 반환값을 보면 WAIT_ABANDONED라는 값이 있다. 

A와 B 쓰레드가 있고 세마포어 오브젝트 C가 있다고 가정해 보면 A 쓰레드가 세마포어 C의 카운트를 하나 감소시켰다. 

그렇다면 다시 카운트를 증가시키는 쓰레드는 A가 된다. 그러나 이러한 제약사항은 뮤텍스 하나만이 지켜주어야 한다.

즉 뮤텍스는 획득한 쓰레드가 직접 반환하는 것이 원칙이다. 현재 뮤텍스를 소유한 본인만이 반환이 가능하다. 그러나 세마포어나 다른 동기화 오브젝트는 현재 커널오브젝트를 소유한 다른 쓰레드가 반환해도 문제가 되지 않는다.

위에 설명한 상황을 좀더 자세히 설명하자면, A쓰레드 B쓰레드 C뮤텍스가 있다고 했을때 쓰레드 A가 뮤텍스 C를 회득했했다면 쓰레드 B는     쓰레드 A가 뮤텍스C를 반환하기를 기다릴 것이다. 그런데 갑자기 예상못한 일이 생겨서 쓰레드 A가 종료되어 버렸다. 즉 뮤텍스를 소유하고 있는 쓰레드가 뮤텍스를 반환하지도 않고 사라진 것이다. 

  이런 상황이 오면 윈도우운영체제는 더이상 정상적인 방법으로 반환이 불가능한 뮤텍스를 대신 반환해주고 다음대기자인 쓰레드 B가 뮤텍스를 소유할 수 있도록 도와준다. 이때 쓰레드 B는 WAIT_ABANDONED값을 반환받게 된다.

 WaitForSingleObject 함수의 반환 값 WAIT_ABANDONED

Posted by JJOREG