원문: http://sol9501.blog.me/70104141478

|소켓 프로그래밍| 멀티 스레드(스레드 동기화) ③

 

* 이번 포스팅은 멀티스레드를 사용하는 경우 두 개 이상의 스레드가 공유 데이터를 접근하는 경우 발생하는 문제를 알아보고 이를 해결하고자 사용하는 동기화 기법인 임계 영역, 뮤텍스, 이벤트, 세마포어등을 알아보겠습니다.

 

 

1. 동기화의 필요성

- 다음의 경우 멀티스레드 사용시 스레드 동기화가 필요하다.

 

● 두개 이상의 스레드가 공유 리소스를 접근할때, 오직 한 스레드만 접근을 허용해야 하는 경우.

 

● 특정 사건 발생을 다른 스레드에게 알리는 경우. 예를 들면, 한 스레드가 작업을 완료한후, 대기중인 다른 스레드를 깨우는 경우

 

간단히 다음의 그림 1을 보면서 설명하겠습니다.

 

그림 1) 스레드 동기화

 

 

- 위의 그림 1과 같은 경우 Thread1과 Thread2는 동시에 공유 변수 5에 접근하고 있습니다. 이 경우 Thread1은 공유 변수 5를 읽어 들여 1을 더하는 작업을 하고 그 결과 값 6을 공유 변수에 다시 쓰고 있습니다. Thread2도 공유 변수 5를 가져와 +1을 하고 그 값을 다시 공유 변수에 쓰고 있습니다. 이 경우 유저는 양쪽의 스레드에서 모두 +1을 했으니 +7의 값을 기대하고 있을 것 입니다. 즉 실제적으로 유저가 기대한 그림은 다음 그림 2와 같은 시나리오 일 것입니다.

 

 

그림 2) 동기화 적용

 

 

 

- 위와 같이 공유 변수에 대한 접근을 하나의 스레드로 제한 함으로써 정확한 값을 리턴 받을 수 있습니다. 즉 이와 같이 멀티스레드 환경에서 발생하는 문제를 해결하기 위한 일련의 작업을 스레드 동기화(Thread Synchronization)라 부릅니다. 윈도우는 다양한 동기화 관련 API를 제공하여 프로그래머가 상황에 따라 적절한 동기화 방법을 선택할 수 있도록 하고 있습니다. 이러한 스레드 동기화 기법은 다음 표1과 같습니다.

 

[표 1] - 스레드 동기화 기법

종류

용도

임계 영역(critical section)

* 공유 리소스에 대해 오직 하나의 스레드 접근만 허용한다.(한 프로세스에 속한 스레드에만 사용 가능)

뮤텍스(mutex)

* 공유 리소스에 대해 오직 하나의 스레드 접근만 허용한다.(서로 다른 프로세스에 속한 스레드에도 사용 가능)

이벤트(event)

* 특정 사건 발생을 다른 스레드에 알린다.

세마포어(semaphore)

* 한정된 개수의 자원을 여러 스레드가 사용하려고 할 때 접근을 제한한다.

 

* 동기화에 대한 더 자세한 내용은 제프리 리처의 "WINDOWS VIA C/C++"나 정덕영 저 "윈도우 구조와 원리"를 추천합니다.

 

 

2. 임계영역

- 임계역역은 두 개 이상의 스레드가 공유 리소스를 접근할 때 오직 한 스레드 접근만 허용해야 하는 경우에 사용한다. 임계 역역은 스레드 동기화를 위해 사용하지만 동기화 객체로 분류하지 않으며 특징 또한 다르다. 대표적인 특징은 다음과 같습니다.

 

● 임계 영역은 유저 영역 메모리에 존재하는 구조체다. 따라서 다른 프로세스가 접근 할 수 없으므로 한 프로세스에 속한 스레드 동기화에만 사용할 수 있다.

● 일반적인 동기화 객체보다 빠르고 효율적이다.

 

임계 영역을 사용을 확인하기 위해 간단한 소스를 작성해 보겠습니다.

 

 

 

- 위의 소스를 그대로 실행하면 hThread[0]과 hThread[1]이 공유 변수 val을 한 번씩 점유해 hThread[0]은 앞에서부터 채워나가고 hThread[1]은 뒤에서부터 값을 채워나가니 결과적으로 배열에는 값 9, 3 이 대략 반반씩 채워지게 됩니다. 결과화면은 그림 3과 같습니다.

 

그림 3) 일반적인 실행화면

 

 

 

- 그렇다면 이번에는 임계 영역을 사용하여 하나의 스레드만이 공유 리소스를 점유하도록 해보겠습니다.

 

우선 CRITICAL_SECTION 구조체 변수를 전역 변수를 선언합니다.

임계 영역을 사용하기 전에 InitializeCriticalSection()함수를 호출하여 초기화 합니다.

공유 리소스를 사용하기 전에 EnterCriticalSection() 함수를 호출합니다. 공유 리소스를 사용하고 있는 스레드가 없다면 EnterCriticalSecrion() 함수는 곧바로 리턴합니다. 공유 리소스를 사용하고 있는 스레드가 있다면 EnterCriticalSecrion() 함수는 리턴하지 않고 해당 스레드는 대기 상태가됩니다.

공유 리소스 사용이 끝나면 LeaveCriticalSection() 함수를 호출합니다.

임계 영역 사용이 끝나면 DeleteCriticalSection() 함수를 호출합니다.

 

 

실행화면은 다음 그림 4와 같습니다. 소스는 위의 소스 1의 주석부분만 풀어주시면 됩니다.

 

그림 4) 임계 영역사용

 

 

 

3. 이벤트

- 이벤트는 특정 사건의 발생을 다른 스레드에 알리는 경우에 주로 사용합니다. 서두에 동기화의 필요성에서

 

● 특정 사건 발생을 다른 스레드에게 알리는 경우. 예를 들면, 한 스레드가 작업을 완료한 후, 대기중인 다른 스레드를 깨우는 경우

 

동기화가 필요하다고 했습니다. 즉 한 스레드가 작업을 완료한 후 대기 중인 다른 스레드가 깨어나서 진행하는 시나리오를 만들 때 이벤트를 이용합니다. 이벤트의 동기화의 간단한 과정은 다음과 같습니다.

 

1. 이벤트를 비신호 상태로 생성한다.

2. 한 스레드가 작업을 진행하고 나머지 스레드는 이벤트에 대해 Wait*()함수를 호출 함으로써 이벤트가 신호 상태가 되기를 기다린다.

3. 스레드가 작업을 완료하면 이벤트를 신호 상태로 바꾼다.

4. 기다리고 있던 모든 스레드가 깨어나서 작업을 진행한다.

 

- 이벤트는 대표적인 동기화 객체로 신호와 비신호라는 두 가지 상태를 가지며 상태를 변경 할 수 있도록 다음과 같은 함수를 제공한다.

 

1) SetEvent

형식 :

Bool SetEvent(HANDEL hEvent);

비신호 상태 - > 신호 상태

 

2) ResetEvent

형식 :

Bool ResetEvent(HANDEL hEvent);

신호 상태 - > 비신호 상태

 

3) CreateEvent

형식 :

HANDLE WINAPI CreateEvent(

__in_opt  LPSECURITY_ATTRIBUTES lpEventAttributes,

__in      BOOL bManualReset,

__in      BOOL bInitialState,

__in_opt  LPCTSTR lpName

);

파라미터    

lpEventAttributes

* SECURITY_ATTRIBUTES 구조체 변수의 주소값을 대입한다. SECURITY_ATTRIBUTES 구조체는 핸들 상속과 보안 디스크립터 정보를 전달하는 용도로 사용된다. 만약 이 값이 NULL이면 핸들은 상속되어 질 수 없다.

bManualReset

* TRUE이면 수동 리셋 이벤트, FALSE이면 자동 리셋 이벤트가 생성된다(표 1참고).

bInitialState 

* TRUE이면 신호 상태로, FALSE이면 비 신호 상태로 시작한다.

lpName 

이벤트를 서로 다른 프로세스에 속한 스레드가 사용(공유)할 수 있도록 이름을 줄 수 있습니다. NULL을 사용하면 이름 없는 이벤트가 생성됩니다.

리턴 값

성공

이벤트 핸들

실패

NULL

 

 

이벤트는 다음과 같은 종류가 있다.

 

[표 1] - 이벤트 특성에 따른 종류

종류

특징

자동 리셋 이벤트

* 이벤트를 신호 상태로 바꾸면 기다리는 스레드 중 하나만 깨운 후 자동으로 비신호 상태가 된다. 따라서 자동 리셋 이벤트에 대해서는 ResetEvent() 함수를 사용할 필요가 없다.

수동 리셋 이벤트

* 이벤트를 신호 상태로 바꾸면 계속 신호 상태를 유지하므로 결과적으로 기다리는 스레드를 모두 깨우게 된다. 자동 리셋 이벤트와 달리 이벤트를 비신호 상태로 바꾸려면 명시적으로 ResetEvent 함수를 호출해야 한다.

 

- 다음의 예제는 마스터 스레드가 버퍼에 값을 쓸 동안 다른 스레드들의 사용을 방지하고 있습니다. 첫 째 마스터 스레드는 비신호와 수동 리셋(manual-reset) 이벤트로 이벤트 오브젝트들을 생성해 초기에 비신호 상태를 유지합니다. 그 후 프로그램에서 요구하는 리더 스레드들을 생성하고 마스터 스레드는 쓰기 작업을 실행합니다. 마스터 스레드의 쓰기 작업이 끝나면 이벤트 오브젝트들은 신호상태로 바뀌고 쓰기 작업을 실행합니다. 실행 화면은 그림 5와 같습니다.

아래 쓰이는 모든 함수들은 멀티 스레드 포스팅에서 모두 다루었던 함수들입니다. 만약 이해가 되지 않으시면 다시 한 번 앞의 포스팅들을 참고 하시길 바랍니다.

 

그림 5) 이벤트 예제

 




원본 소스: http://msdn.microsoft.com/en-us/library/ms686915%28v=VS.85%29.aspx

'PROGRAMMING > MFC(C++)' 카테고리의 다른 글

[0421수업] 세마포어  (0) 2011.04.21
[0418수업] ThreadCrash(Critical Section, 뮤텍스)  (0) 2011.04.18
[0415수업] UIThread(Suspend Thread, Resume Thread)  (0) 2011.04.15
[0415수업] Thread  (0) 2011.04.15
[0414수업] WorkerThread  (0) 2011.04.14
Posted by 마마필로 :