본문 바로가기
WORK/Sotfware

MFC Thread 완전정복 ( 마지막 )

by KANG Stroy 2008. 6. 2.
728x90
728x90

-Event

SetEvet(): 이벤트를 signaled 상태로 설정한다.
ResetEvent(): 이벤트를 non-signaled상태로 설정한다.
PulseEvent(): 한번의 operation으로 셋과 리셋을 수행한다.
블록킹된 스레드는 이벤트가 signaled일때 해제(릴리즈)되어 나온다.

하나의 스레드가 CEvent::Lock으로 블록킹되어 이벤트가 set되기를 기다리고 있다. 다른 스레드가 이벤트를 set하면 기다리던 스레드는 릴리즈된다. 모든 기다리고 있는 스레드들은 이벤트가 set될때 릴리즈 된다.

윈도우즈는 2개의 다른 이벤트를 제공한다.


1.오토리셋 이벤트
블록킹된 스레드가 해제되면, 자동적으로 non-signaled로 리셋된다.

2.수동리셋 이벤트
블록킹된 스레드가 해제되면, 자동적으로 non-signaled로 리셋되지 않는다.


자동으로 할지 수동으로 할지는 다음사항에 따라 결정하는것이 좋다.
- 단지 하나의 스레드가 이벤트로 사용되어 질경우, 자동리셋 이벤트를 사용해라. SetEvent로 기다리고 있는 스레드를 릴리즈시켜라. 릴리즈되는 순간, 이벤트는 자동리셋되기때문에 ResetEvent를 호출할 필요가 없다.
- 둘이상의 스레드가 이벤트로 사용되어질경우, 수동리셋이벤트를 사용하라. PulseEvent로 모든 기다리는 스레드들을 릴리즈 시켜라. 스레드들이 모두 릴리즈된 이후 PulseEvent가 이벤트를 리셋하기에 ResetEvent를 호출할 필요가 없다. SetEvent로 했을 경우에는 모든 스레드가 릴리즈되는 것을 보장하지 못한다.
오로지 PulseEvent만이 모든 스레드들을 릴리즈 시켜준다. PulseEvent는 set,reset를 해줄뿐아니라, 이벤트를 기다리는 모든 스레드들을 릴리즈시키는것을 보장해준다.


CEvent (BOOL bInitiallyOwn = FALSE,
    BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL,
    LPSECURITY_ATTRIBUTES lpsaAttribute = NULL)


bInitiallyOwn : 이벤트오브젝트가 초기에 signaled,non-sig인지를 결정한다.
bManualReset : 수동인지,자동인지 결정.
lpszName : 이벤트오브젝트 이름.뮤텍스처럼, 이름을 명기해야한다. 이는 다른 프로세스(어플리케이션)에서도 사용되어 지기 위해 같은 이름으로 하여야 한다.같은 프로세스에서 사용하는 것이라면, NULL이어도 된다.
lpsaAttribute : 보안속성. 걍 NULL로 설정.


- 자동리셋의 예

CEvent g_event; //global , Auto-reset,Initially non-signaled


//Thread A
Init(&buffer);
g_event.SetEvent();


//Thread B
g_event.Lock();
Manage(&buffer);


buffer라는 변수를 2개의 스레드가 사용한다. buffer가 먼저 초기화 된 이후 buffer를 사용하게끔 하기위해서 위와 같은 코딩을 한다. B는 buffer가 초기화가 되고 SetEvent가 호출될때까지 Lock에서 기다린다. A는 buffer를 초기화 하고 SetEvent를 호출하여 event signaled로 설정한다.그리하여 B는 릴리즈가 되고 초기화된 버퍼를 가지고 논다.

Lock()의 리턴값이 0이면, TimeOut expired or error occured. 0이 아니면, signaled로 리턴.
Lock의 첫번째 파라미터는 타임아웃시간임. 초기값은 infinite.


- 수동 리셋의 예

CEvent g_event(FALSE,TRUE); //Initially non-signaled, manual-reset


//Thread A
Init(&buffer);
g_event.PulseEvent();


//Thread B
g_event.Lock(); //waiting for signal


//Thread C
g_event.Lock(); //waiting for signal


Event는 또다른 방식으로 사용할 수 있다. 즉, 스레드 B는 단지 A가 어떠한 작업이 완료됬는지에 따라서 기다리지 않고 행동을 결정하고 싶어할 경우이다.
이는 아래와 같은 방법으로 사용가능하다.


CEvent g_event( FALSE, TRUE );//Initally non-signaled, manual-reset

//Thread A
IsCompleted(&buffer);
g_event.SetEvent();


//Thread B
if( ::WaitSingleObject( g_event.m_hObject, 0 ) == WAIT_OBJECT_0 )
{
 //when signaled
}
else
{
 //when non-signaled
}


-Semaphores
세마포어는 공유된 리소스를 특정갯수의 스레드만 동시에 사용할 수 있도록 한다. 즉, 10개의 스레드가 움직이고 있다. 각기 스레드는 데이타를 모으고 다 채우면, 소켓통신으로 어디론가 보내는 구조이다. 소켓은 최대 3개뿐이다.
이럴경우, 세마포어가 사용된다. 3개 스레드만 작업을 하고 나머지는 기다리게 하는 동기화 작업을 하는 것이다.

세마포어는 리소스 카운트로 동기화를 시킨다. Lock을 했을 경우 리소스 카운트는 감소하고 그반대는 증가한다. 스레드 하나가 Lock함수를 수행할 경우, 리소스 카운트는 감소하여 0이 되면, 그 스레드는 블록된다. 이경우 다른 스레드가 UnLock함수를 수행하여 리소스 카운트를 증가하여 1이 되면, 블록에서 해제된다. 즉, 리소스 카운트가 0일 경우 블록된다.
세마포어는 하나의 프로세스내에서 또는 다른 프로세스에 속하는 스레드들 내에서 동기화 할 경우 쓰여진다.


CSemaphore(
 initial resource count
 ,maximum resource count
 ,sema name = null
 ,보안속성 = null )


CSemaphore g_sema(3,3);

g_sema.Lock();
//Access the shared resource.
g_sema.Unlock();


스레드 A가 위의 것을 수행하면, 리소스 카운트는 3->2 로 되고,
스레드 B가 위의 것을 수행하면, 리소스 카운트는 2->1 로 되고,
스레드 C가 위의 것을 수행하면, 리소스 카운트는 1->0 로 되고,
스레드 D가 위의 것을 수행하면, 리소스 카운트는 0이기에 수행되지 않고 Lock에서 블록킹된다.
그리고 스레드 A,B,C중 어느 것 하나가 UnLock함수를 수행하면 리소스 카운트가 0->1로
증가하여 스레드 D의 블록킹은 해제된다. Lock이 수행되었기에 다시 1->0이 된다.
즉 공유된 리소스를 딱 3개의 스레드만 허용한다는 의미이다.

Lock함수는 역시나 타임아웃시간을 설정할수 있다.
리소스 카운트가 0이 아니거나, 타임아웃 걸렸을 경우에나 블록킹에서 해제된다.

UnLock함수는 이전의 리소스 카운트를 알아내고 거기서 1증가시킨다.


LONG lPrevCount;
g_semaphore.Unlock (2, &lPrevCount);


Unlock( lCount, &lPrevCount )

이전 리소스 카운트를 알수 있다.


-The CSingleLock and CMultiLock Classes


이들 클래스들은 CriticalSection,mutexes,CEvent,CSemaphore를 래핑한다. 이들의 용도는 바로 아래와 같다.

1. CSingleLock


CCriticalSection g_cs;

CSingLock lock(&g_cs);

lock.Lock();
...(A)
lock.Unlock();


만약 (A)지점에서 exception이 발생했을 경우, CSingleLock으로 감싸지않은 CCriticalSection은 Unlock을 영원히 수행할수없기에 다른 스레드는 계속해서 블록킹되어 있게된다.
하지만 위에 처럼 래핑을 시켜놓으면,
exception발생시, CSingLock의 소멸자가 수행되고 이안에서 Lock이된 것들을 모두 UnLock으로 풀어준다.


2. CMultiLock


여러종류의 동기화를 함께 사용할때 쓰여진다. 단, Criticalsection 은 CMultilock을 래핑이 안된다.


CMutext g_mutex;
CEvent g_event[2];
CSyncObject* g_pObjects[3] = { &g_mutext,&g_event[0], &g_event[1] };
CMultiLock lock( g_pObjects,3);


lock.Lock(); //모든 스레드 객체가 signaled이 될 경우 릴리즈

or

lock.Lock(INFINITE,FALSE); //셋중 하나만 signaled이 될 경우 릴리즈


CMultiLock::Lock(
 timeout
 ,bIsAll = TRUE
 ,wakeupmask = 0 )


bIsAll : TRUE=모든 스레드가 signaled될때 릴리즈.
 FALSE=어느 하나가 signaled될때 릴리즈
wakeupmask :  임의 특정메시지로 인해 wakeup모드를 설정할 수있다.



Lock의 리턴값의 의미


CMultilock lock( g_pObject, 3 );
DWORD dwResult = lock.Lock(INFINITE,3);
DWORD nIndex = dwResult - WAIT_OBJECT_0;
if( nIndex == 0 )
 // The mutex became signaled.
else if (nIndex == 1 )
 // The first event became signaled.
else if (nIndex == 2 )
 // The second event became signaled.  


만약 INFINITE로 설정을 안했을 경우에는 WAIT_OBJECT_O 을 빼기전에 dwResult == WAIT_TIMEOUT 인지를 비교해야 한다. 

좀 더 자세한 내용은 CMultilock::Lock 함수를 MSDN에서 있으니 반드시 참고하도록...


-Writing Thread-Safe Classes

MFC클래스들은 클래스레벨에서는 스레드 안전하지만, 오브젝트 레벨에서는 그렇지 못하다. 다시 말하자면, 같은 클래스의 다른 객체들을 두개의 스레드가 접근하는 것은 안전하지만, 같은 객체를 접근하는 것은 그렇지 못하다. 이유는 성능상의 이유로 오브젝트단에서는 적용하지 않았다. 언록된 Criticalsection을 lock시키는 것은 cpu의 수백의 클락사이클을 소비한다.

다음의 예를 보자


CString g_strFileName;

//Thread A
g_strFileName = pszFile;


//Thread B
pDB->TextOut( x,y,g_strFileName );


이럴경우 화면에 출력되는 것은 무엇이 될까? 새값일까?이전값일까? 아마도 아무것도 아닐것이다. CString객체로 집어넣는 과정에서 B가 수행될경우, 일련의과정들을 방해받는다. 그래서 출력값은 짤리거나 에러로 뜰 가능성이 크다. 그래서 CriticalSection 으로 동기화를 해야한다. 예제는 생략

728x90

'WORK > Sotfware' 카테고리의 다른 글

Edit Clear  (0) 2008.06.02
RS232 통신  (0) 2008.06.02
MFC Thread 완전정복 3 - 동기화  (0) 2008.06.02
MFC Thread 완전정복 2 - Thread 종료하기  (0) 2008.06.02
MFC Thread 완전정복 1(펌)  (0) 2008.06.02

댓글