본문 바로가기

Windows/_System Programming

쓰레드(Thread) 메모리 접근 동기화

1. 크리티컬 섹션 기반의 동기화(유저 모드) :

개념 :

열쇠(Critical Section)를 생성한다.

쓰레드가 임계 영역에 접근하기 위해서는 열쇠를 얻어야 한다.

열쇠를 얻어 임계 영역에 접근한 쓰레드는 일을 마친후 다음 쓰레드를 위해 열쇠를 반환한다.


사용법 :

CRITICAL_SECTION gCriticalSection;              // 열쇠를 생성한다
                   // 크리티컬 섹션 오브젝트 선언후에는 반드시 초기화 해야한다.
void InitializeCriticalSection(                           // 크리티컬 섹션 오브젝트 초기화 함수   
        LPCRITICAL_SECTION lpCriticalSection  // 초기화 하려는 크리티컬 섹션 오브젝트의 주소값
);

void EnterCriticalSection(
        LPCRITICAL_SECTION lpCriticalSection // 획득하려는 크리티컬 섹션 오브젝트의 주소값
);

열쇠(크리티컬 섹션 오브젝트)를 획득한다.

다른 쓰레드가 열쇠를 소유한 상태이면 반환할 때까지 블로킹된다.

열쇠가 반환되면 블로킹 상태에서 빠져나와 열쇠를 획득하여 임계 영역에 접근한다.
(이때 쓰레드가 크리티컬 섹션 오브젝트를 획득했다고 한다)

void LeaveCriticalSection(
        LPCRITICAL_SECTION lpCriticalSection // 반환하려는 크리티컬 섹션 오브젝트의 주소값
);

임계 영역을 빠져 나와 열쇠를 반환한다.

호출이 완료되면 크리티컬 섹션 오브젝트를 반환했다고 표현한다

만약 EnterCriticalSection 을 호출하고 블로킹 상태에 놓인 쓰레드가 있다면

이 함수 호출로 인해 블로킹 상태에서 빠져 나와 열쇠를 획득해 임계 영역에 접근한다.

void DeleteCriticalSection(
        LPCRITICAL_SECTION lpCriticalSection // 리소스 해제할 크리티컬 섹션 오브젝트의 주소값
);

크리티컬 섹션 오브젝트 초기화 함수가 호출되는 과정에서 할당된 리소스들을 반환한다.

(참고 : 임계 영역 접근 동기화를 위하여 열쇠(CriticalSection)을 사용한다.

하지만 여러 쓰레드들이 이 열쇠를 획득하려 하면(EnterCriticalSection 함수 호출) 어떻게 될까?

이 함수에 대해서도 동기화를 해야 하지 않을까?

답은 '필요없다' 이다.

이 함수는 컨텍스트 스위칭을 막으므로 동기화 해줄 필요가 없다)



2. 인터락 함수(Interlocked Family Of Function) 기반의 동기화(유저 모드) :

개념 :

전역변수 하나의 접근방식을 동기화 할때 사용한다.

내부적으로 한 순간에 하나의 쓰레드만 실행되도록 동기화 되어 있다.(원자적 접근을 보장한다)

(원자적 접근(Atomic Access) : 한순간에 하나의 쓰레드만 접근)


사용법 :

LONG InterlockedIncrement(
        LONG volatile *Addend       // 값을 하나 증가시킬 32비트 변수의 주소값
);

LONG InterlockedDecrement(
        LONG volatile *Addend       // 값을 하나 감소시킬 32비트 변수의 주소값
);

(참고 : 이외에도 MS 에서는 다양한 인터락 함수들을 제공한다. MSDN 을 참조하자

volatile 의미 :

1) 최적화를 수행하지 마라.   // 해당 소스 그대로 컴파일 된다.
(a=1, a=2, a=3  최적화 컴파일->  a=3,      a=1, a=2, a=3  컴파일->  a=1, a=2, a=3)

2) 메모리에 직접 연산하라.   // 해당 데이터는 캐시되지 않고 데이터 전송이 바로 이뤄진다)



3. 뮤텍스(Mutex) 기반의 동기화(커널 모드) :

개념 :

크리티컬 섹션 오브젝트와 똑같다.(대상이 CriticalSection -> 뮤텍스)


뮤텍스 커널 오브젝트의 상태 :

Non-Signaled : 뮤텍스가 쓰레드에 의해 획득되어 있을 때

Signaled : 뮤텍스가 쓰레드에 의해 획득이 가능할 때


사용법 :

HANDLE CreateMutex(
         LPSECURITY_ATTRIBUTES lpMutexAttributes, // 뮤텍스 보안속성
         BOOL bInitialOwner,  // TURE : 뮤텍스를 생성하는 쓰레드가 먼저 기회를 얻는다.
                                        // FALSE : 뮤텍스를 먼저 획득하는 쓰레드가 기회를 얻는다.
         LPCTSTR lpName     // 뮤텍스에 이름을 붙여주기 위해 사용한다.(NULL 이 아닐경우 NamedMutex)
);

뮤텍스는 획득 가능할 때 Signaled 상태이므로

WaitForSingleObject 함수로 뮤텍스를 획득한다.

함수를 호출하고 나면 Signaled -> Non-Signaled 상태로 자동 변경된다.

BOOL ReleaseMutex(
         HANDLE hMutex       // 반환할 뮤텍스의 핸들 (Non-Signlaed -> Signaled)
);


사용을 완전히 마친 뮤텍스는

CloseHandle() 함수를 사용하여 핸들을 반환한다.



4. 세마포어(Semaphore) 기반의 동기화(커널 모드) :

개념 :

세마포어는 뮤텍스 + 카운트(Count) 이다.

즉, 쓰레드의 메모리 동시접근을 지정한 갯수만큼 허용한다는 것이다.
(뮤텍스는 하나의 쓰레드만 가능하다)

세마포어는 lInitialCount 에 의해 초기 카운트가 결정된다.

임계 영역에 접근한 쓰레드가 생기면 카운트 -1 이 되고
임계 영역에서 빠져 나온 쓰레드가 생기면 카운트 +1 이 된다.

카운트 0 : Non-Signaled (획득 불가능한 상태)
카운트 1 이상 : Signaled (획득 가능한 상태)


사용법 :

HANDLE CreateSemaphore(
         LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,  // 보안속성
         LONG lInitialCount,          // 임계 영역에 접근 가능한 쓰레드의 개수
         LONG lMaximumCount,   // 세마포어가 지닐수 있는 값의 최대 크기(1일경우 뮤텍스와 동일한 기능)
         LPCTSTR lpName           // 세마포어에 이름(NULL 아닐 경우 이름있는 세마포어(Named Semaphore)
);

WaitForSingleObject 함수로 임계영역에 접근한다(세마포어 카운트 -1)

BOOL ReleaseSemaphore(
         HANDLE hSemaphore,          // 반환하고자 하는 세마포어의 핸들
         LONG lReleaseCount,           // 증가시킬 카운트 값의 크기(세마포어 생성시 결정한
                                                                                            최대 카운트값을 넘길 경우 FALSE 반환)
         LPLONG lpPreviousCount      // 변경되기 전 세마포어 카운트 값을 저장할 변수(필요없다면 NULL)
);


사용을 완전히 마친 세마포어는

CloseHandle() 함수를 사용하여 핸들을 반환한다.



5. 이름있는 뮤텍스(or 세마포어) 기반의 프로세스 동기화(커널 모드) :

개념 :

뮤텍스와 세마포어를 생성할 때 이름을 붙여 줄수 있다.

이름이 붙여지면 서로 다른 프로세스에 존재하는 쓰레드들끼리의 동기화가 가능하다.

사용법 :

HANDLE OpenMutex(
         DWORD dwDesiredAccess,    // 이름 있는 뮤텍스로의 접근권한 (MUTEX_ALL_ACCESS 전달)
         BOOL bInheritHandle,            // 핸들의 상속 유무 결정 
         LPCTSTR lpName                // 얻고자 하는 핸들 정보의 커널 오브젝트 이름(핸들 테이블에 등록된다)
);


(추가 : 뮤텍스의 소유와 WAIT_ABANDONED

두 개의 쓰레드 A, B 와 뮤텍스 C가 존재한다.

쓰레드 A가 뮤텍스 C를 획득했다.

쓰레드 B는 쓰레드 A가 뮤텍스 C를 반환하기를 기다리고 있다.

이때 쓰레드 A가 미처 반환하지 못하고 종료되어 버렸다.

그러면 Windows 가 쓰레드의 상태와 뮤텍스의 상태를 예의 주시하다가 문제가 발생했음을 인식하고

쓰레드 A 대신에 뮤텍스 C를 반환하여 쓰레드 B 가 획득할수 있게 한다.

이때 쓰레드 B는 WAIT_ABANDONED 을 반환받는다.

참고로 뮤텍스는 뮤텍스를 획득한 쓰레드가 직접 반환하는 것이 원칙이지만 세마포어와 그 이외의 동기화 오브젝트는 획득한 쓰레드가 직접 반환하지 않더라도(누가 대신 반환해도) 문제가 되지 않는다)

'Windows > _System Programming' 카테고리의 다른 글

쓰레드(Thread) 동기화 개념  (0) 2010.03.04
쓰레드(Thread) 실행순서 동기화  (0) 2010.03.04
쓰레드(Thread)의 생성과 소멸  (0) 2010.02.28
쓰레드(Thread)  (2) 2010.02.28
환경변수  (0) 2010.02.25