2006년 11월 8일

WIN32 동기화 객체 자동으로 안되겠니?

Win32 API를 통해 멀티 쓰레드와 관련된 어플리케이션을 개발하다보면 동기화 때문에 가끔 작업하던 컴퓨터를 리셋해야 하는 경험을 하곤 한다.
이런 삽질을 피하려면 항상 임계영역(Critical Section)에 진입해 상호배제적인 작업을 마친후에는 사용중이던 동기화객체를 반드시 해제하여 데드락에 빠지지 말아야 한다.

허나 프로그램은 불완전한 인간이 하는일 어찌 실수치 않으리오 …
동기화 객체 해제하는 코드를 빠뜨리는 날에는 지금껏 알던 수많은 욕들이 입속에서 메아리치게 된다. 예전 본인의 모습이다...~_~;;

CRITICAL_SECTION 동기화 객체의 경우 LeaveCriticalSection()을 호출하지않고 뻘짓거리를 하고 있을 때 이 임계영역을 진입하려는 다른 녀석은 무한정 대기하게 된다 아마 한달정도면 데드락이 풀릴지도 모른다…(믿거나 말거나)

해서 이러한 동기화 객체들을 모아모아 좀더 쉽게 설계하자는 것이 이 글의 목표이다. 일단 금번 버전에서는 윈도우의 가장 흔힌 동기화 객체인 CRITICAL_SECTION, EVENT, MUTEX, SEMAPHORE 을 대상으로 하려한다.

(참고) 자세히 보면 대부분의 동기화 객체들은 첫번째 인자로 보안과 관련된 항목을 집어넣게 되어있다. 이 보안항목을 집어넣는 녀석들은 99% 커널 오브젝트다.


1. ISynchronize - 인터페이스의 설계
모든 동기화 객체는 ISynchronize 란 인터페이스를 상속받아 구현한다. 이 인터페이스를 통해 자동적으로 임계구역에 진입하고 탈출하도록 디자인할 계획이다.

#ifndef _SynchronizeH
#define _SynchronizeH

#include
namespace Comeric
{
class __declspec( dllexport ) ISynchronize
{
public:
virtual DWORD enter(DWORD dwMilliSeconds=INFINITE)=0;
virtual void leave()=0;
};
}
#endif


2. CRITICAL_SECTION
#ifndef _CriticalSectionH
#define _CriticalSectionH
#include
#include "Synchronize.h"

namespace Comeric
{
class __declspec( dllexport ) CriticalSection
: public ISynchronize
{
public:
virtual DWORD enter(DWORD dwMilliSeconds=INFINITE)
{
EnterCriticalSection(&_cs);
return WAIT_OBJECT_0;
}
virtual void leave()
{
LeaveCriticalSection(&_cs);
}
protected:
CRITICAL_SECTION _cs;
public:
CriticalSection()
{
InitializeCriticalSection(&_cs);
}
virtual ~CriticalSection()
{
DeleteCriticalSection(&_cs);
}
};
}
#endif


3. EVENT
#ifndef _EventH
#define _EventH

#include "Synchronize.h"

namespace Comeric
{
class __declspec( dllexport ) Event
: public ISynchronize
{
public:
virtual DWORD enter(DWORD dwMilliSeconds=INFINITE)
{
return WaitForSingleObject(_hEvent, dwMilliSeconds);
}
virtual void leave()
{
SetEvent(_hEvent);
}

protected:
HANDLE _hEvent;
public:
Event(std::wstring name = NULL)
{
if (NULL == OpenEvent(EVENT_ALL_ACCESS, FALSE, name.c_str()) )
_hEvent = CreateMutex(NULL, FALSE, /*auto reset*/ TRUE, name.c_str())

assert(_hEvent);
}
virtual ~Event()
{
CloseHandle(_hEvent);
}
};
}
#endif


4. MUTEX

#ifndef _MutexH
#define _MutexH
#include
#include
#include
#include "Synchronize.h"

namespace Comeric
{
class __declspec( dllexport ) Mutex
: public ISynchronize
{
public:
virtual DWORD enter(DWORD dwMilliSeconds=INFINITE)
{
return WaitForSingleObject(_hMutex, dwMilliSeconds);
}
virtual void leave()
{
BOOL ret=ReleaseMutex(_hMutex);
assert(ret != NULL);
}
protected:
HANDLE _hMutex;
public:
Mutex(std::wstring name = NULL)
{
if (NULL == OpenMutex(MUTEX_ALL_ACCESS, FALSE, name.c_str()) )
_hMutex = CreateMutex(NULL, TRUE, name.c_str());
assert(_hMutex);
}
virtual ~Mutex()
{
CloseHandle(_hMutex);
}
};
}
#endif


5. SEMAPHORE

#ifndef _SemaphoreH
#define _SemaphoreH
#include "Synchronize.h"

namespace Comeric
{
class __declspec( dllexport ) Semaphore
: public ISynchronize
{
public:
virtual DWORD enter(DWORD dwMilliSeconds=INFINITE)
{
return WaitForSingleObject(_hMutex, dwMilliSeconds);
}

virtual void leave()
{
BOOL ret= ReleaseSemaphore(_hSemaphore, 1 , NULL);
assert(ret != NULL);
}

protected:
HANDLE _hSemaphore;

public:
Semaphore(std::wstring name = NULL, LONG lInitialCount=1, LONG lMaximumCount=1) //default binary semaphore
{
if (NULL == OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, name.c_str()) )
_hSemaphore = CreateSemaphore(NULL, TRUE, name.c_str());

assert(_hSemaphore);
}
virtual ~Semaphore()
{
CloseHandle(_hSemaphore);
}
};
}
#endif


자 그럼 우리의 목표인 자동적인 임계구역 진입, 탈출을 적용해 보자. 이미 짐작했겠지만 모든 동기화 객체가 동일한 인터페이스를 지니고 있기 때문에 매우 쉽게 구현가능하다. 이런 녀석을 AutoSynchronize 라고 이름짓자.

6. AutoSynchronize
이 녀석을 만들기 위해 지금까지 삽질을 했단 말이다...~


#ifndef _AutoSynchronizeH
#define _AutoSynchronizeH

#include
#include "Synchronize.h"
namespace Comeric
{
class __declspec( dllexport ) AutoSynchronize
{
public:
AutoSynchronize(ISynchronize& iSynchronize)
: _pSynchronize(&iSynchronize)
{
_pSynchronize->enter();
}

virtual ~AutoSynchronize()
{
_pSynchronize->leave();
}
protected:
ISynchronize* _pSynchronize;
};
}
#endif

짐작했겠지만 이제 동기화 객체를 사용하는 것은 너무나 쉬워졌다.
AutoSynchronize 녀석의 생성,소멸자에서 자동으로 임계구역에 진입, 탈출을 해주니 말이다.
단순히 다음과 같이 시도하면 될것이다.

활용예

Comeric::CriticalSection cs;

쓰레드1,쓰레드2의 임계영역은 서로 동기화된다.
쓰레드 1
{
Comeric::AutoSynchronize as(cs); //as 는 스택변수이기 때문에 해당 구역이 존재하는한 동기화가 유지된다.
// 상호배타적인 작업

}

쓰레드 2
{
Comeric::AutoSynchronize as(cs); //as 는 스택변수이기 때문에 해당 구역이 존재하는한 동기화가 유지된다.
// 상호배타적인 작업들…
}

위에서 구현한 동기화 객체들에 대한 비교는 다음표를 참고하기 바란다.

Synchronization Objects Summary
Name
Relative speed Cross process Resource counting
Critical Section
Fast No No (Exclusive Access
Mutex
Slow Yes No (Exclusive Access)
Semaphore
Slow Yes Yes
Event
Slow Yes Yes*
Metered Section
Fast Yes Yes

처음 보는 Metered Section 은 CRITICAL_SECTION 처럼 빠르면서 프로세스간 동기화에 사용할수있는 방법으로 http://msdn2.microsoft.com/en-us/library/ms810428.aspx 에서 해당 구현을 참고할수있다.
이마저도 귀찮아할줄 알고 직접 Metered Section을 ISynchronize 인터페이스에 맞게
VS2005에서 확장 DLL 로 구현해 놓았다. - 다운로드는 이곳

- 친절한 주인장백

댓글 없음:

댓글 쓰기

시리우스 라이브러리 홈페이지 오픈

현재 시리우스(Sirius) 라이브러리라는 제품을 개발하고 이를 소개하는 홈페이지를 오픈 하였습니다. 관심있는 분들의 많은 방문 요청드립니다. 앞으로 업데이트 소식및 변경사항은 스파이럴랩 홈페이지를 통해 진행할 예정입니다. 스파이럴랩 홈페이지 :  h...