본문 바로가기
programing/Thread

쓰레드 동기화

by RedWiz 2015. 4. 30.

 

유저 모드 : 응용 프로그램의 실행 모드

응용 프로그램이 실행되는 기본모드로, 물리적인 영역으로 접근이 허용되지 않으며, 접근할 수 있는 메모리의 영역에도 제한이 따름

 

커널 모드 : 운영체제의 실행 모드

운영체젝 실행될 때의 모드로, 메모리뿐만 아니라, 하드웨어의 접근에도 제한이 따르지 않음

 

1. 유저 모드 동기화

- 커널에서 제공하는 동기화 기능 사용 안함 -> 성능상 이점,  기능상 제한

- "속도가 빠르다"

※ 동기화 기능이 유저 모드에서 일어날 뿐, 락 걸려서 스레드가 대기 모드로 바뀌어 컨텍스트 스위칭 일어나면 얄짤없이 커널 모드로 들어가게 된다.

 

2. 커널 모드 동기화

- 커널에서 제공하는 동기화 기능 사용, 커널 모드 전환 필요 -> 성능 저하, 기능 제공 받음

- Signal & Non-Signal 상태 존재     

쓰레드는 자신이 대기 중인 오브젝트가 논시그널 상태인 동안은 스케줄이 되지 않고 있다가, 오브젝트가 시그널 상태가 되면 스케줄 가능 상태가 되어 곧바로 수행을 재개하게 된다.

> Signal 상태 : 대기 함수(WaitforSingleObject, MultiObjects) 빠져나옴, 만나도 대기 안함

> Non-Signal 상태 : 대기 함수 만나면 멈출 수 있는 상태

- 임계 영역( Critical Section ) 접근 동기화

> 임계 영역 : 배타적 접근(하나의 쓰레드 씩 만)이 요구되는 공유 리소스(전역변수 같은)에 접근하는 코드 블록

- 유저모드 동기화에 비해 제공되는 기능이 더 많음

- Dead-lock에 걸리지 않도록 타임아웃의 지정이 가능하다.

 

유저 모드 동기화

크리티컬 섹션(메모리 접근 동기화)

인터락(메모리 접근 동기화)

 

커널 모드 동기화

뮤텍스(메모리 접근 동기화)

세마포어(메모리 접근 동기화)

이름 있는 뮤텍스(프로세스간 동기화)

이벤트(실행 순서 동기화)

 

cf. 실행순서 동기화

- 메모리 접근하는 쓰레드의 실행순서를 동기화(메모리 접근 동기화를 포함하는 개념)

- 생산자 소비자 모델 (생산 -> 소비의 순서,

입력과 출력의 속도가 다를경우 각각 쓰레드 돌리고 사이에 버퍼 끼움)

 

================================================================================

================================ USER MODE ==================================

================================================================================

Critical Section

 

// critical section object

CRITICAL_SECTION    gCriticalSection;   

 

// 초기화 하여 리소스 할당

void InitialCriticalSection( LPCRITICAL_SECTION    lpCriticalSection );

 

// 크리티컬 섹션 오브젝트 획득

void EnterCriticalSection( LPCRITICAL_SECTION    lpCriticalSection );

(cf.interupt와 연관되어서  disable일 경우 클럭 신호가 전달 되지 않아 컨텍스트 스위칭이 안일어난다.)

 

// 크리티컬 섹션 오브젝트 반환

void LeaveCriticalSection( LPCRITICAL_SECTION    lpCriticalSection );

 

// 할당 해제

void DeleteCriticalSection( LPCRITICAL_SECTION    lpCriticalSection );

 

cf. InitializeCriticalSectionAndSpinCount()

스핀 카운트를 설정할 수 있음

리턴 타입이 BOOL타입

(http://girtowin.tistory.com/m/107)

(https://m.blog.naver.com/PostView.nhn?blogId=devmachine&logNo=220151128032)


(http://workerant-textcube.blogspot.com/2010/12/스레드-동기화.html)


Interlock

- 변수 하나만 접근 방식을 동기화 할 경우

- CriticalSection 방법은 Interlock 함수 기반

 

LONG InterlockedIncrement( LONG volatile* Addend );

 

LONG InterlockedDecrement( LONG volatile* Addend );

 

cf. volatile : 1. 최적화 수행 안함    2. 메모리 영역을 캐쉬 하지 않고 직접 접근 연산


cf. 원자적 접근하여 연산(atomic operation)하는 것과 관련 있다.


단, volatile을 이용하는데 있어서...

c++03에서 언어 규격 상 쓰레드의 정의가 없으며, 싱글 쓰레드 환경만을 생각하고 규격을 정의 했다.

멀티쓰레드 관련 부분은 각 컴파일러마다 독자적으로 구현되었다.

c++11에서는 쓰레드에 대해 명시 돼있으며 C++11에서 volatile의 메모리 배리어 동장은 명시가 돼있지 않다.

따라서 c++11이상 환경에서는 atomic 사용을 권장한다.


================================================================================

================================ KERNEL MODE ================================

================================================================================

Mutex

- 뮤텍스 <- 커널 오브젝트

- Signaled, Non-Signaled 이용

 

HANDLE CreateMutex

LPSECURITY_ATTRIBUTES lpMutexAttributes,    // 보안 속성

BOOL bInitialOwner,                                       // 

LPCTSTR lpName                                           // 뮤텍스 이름

);

bInitialOwner : TRUE - 뮤텍스를 생성하면서 임계영역 접근 권한을 갖음 (Non-Signaled)

    FALSE - 뮤텍스만 생성하고 실제 사용권한을 얻을때 대기함수 이용해야함 (Signaled)

( 크리티컬 섹션 객체 생성 및 + CriticalSection의 InitializeCriticalSection() 역할 )

 

WaitForSingleObject()

- 핸들의 커널 오브젝트를 Signaled 상태로 반환하는 경우 Non-Signaled로 바꿈

( CriticalSection의 EnterCriticalSection() 역할 )

 

BOOL ReleaseMutex(

HANDLE hMutex

);

- 뮤텍스를 Signaled 상태로 만듦

( CriticalSection의 LeaveCriticalSection() 역할 )

 

CloseHandle()

- 핸들을 반환하고 리소스를 해제 함

( CriticalSection의 DeleteCriticalSection() 역할 )

 

cf. WaitForSingleObject(), WaitForMultipleObject()

- 커널 오브젝트의 Signaled 상태를 기다리고 커널 오브젝트 상태를 알 수 있는 함수

 

DWORD WaitForSingleObject(

HANDLE hHandle,                 // 핸들

DWORD dwMilliseconds        // INFINITE : 커널 오브젝트가 Signaled 될때까지 무한정 기다림  

);

 

return :  WAIT_OBJECT_0    : 커널 오브젝트가 Signaled 일때

WAIT_FAILED        : 함수 실패시

WAIT_TIMEOUT     : Signaled가 되지 않고 dwMilliseconds 시간이 다 된 경우

WAITABANDONED  : 소유 관계와 관련하여 함수가 정상적이지 못한 오류 발생

 

DWORD WaitForMultipleObject(

DWORD nCount,                    // 배열에 저장된 핸들 개수

const HANDLE* lpHandles,     // 핸들 배열 주소 정보

BOOL bWaitAll,                      // TRUE : 모두 Signaled, FALSE : 하나라도 Signaled

DWORD dwMilliseconds         //

);

 

Semaphore

- 단순화된 세마포어(바이너리 세마포어) = 뮤텍스 ( 뮤텍스는 세마포어의 일종 )

- 뮤텍스와 다르게 Count 기능 존재

 

HANDLE CreateSemaphore (

LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,    // 보안속성

LONG lInitialCount,          // 초기 카운트, 0 - Non-Signaled, 1 이상 - Signaled

LONG lMaximumCount,    // 세마포어가 가질 수 있는 값의 최대 크기

LPCTSTR lpName            // 세마포어 이름

);

- lInitialCount는 열쇠의 갯수에 해당됨 ( Mutex는 lInitialCount가 1 )

- WaitingForSingleObject() 함수 호출시 Count가 하나씩 떨어짐, 0이 되면 Blocking 상태됨

 

BOOL ReleaseSemaphore (

HANDLE hSemaphore,        // 반환할 세마포어 핸들

LONG lReleaseCount,        // 세마포어 카운트 증가량

LPLONG lpPreviousCount   // 증가 이전 값 (필요 없으면 NULL)

);

- return : zero : 함수 실패시

- lReleaseCount 만큼 Count를 증가시킴

- 최대 카운트 값이 넘어갈 경우 변경안되고 FALSE 반환

 

 

Named Mutex

- Named Mutex(이름있는 뮤텍스), Named Semaphore(이름있는 세마포어)

- 뮤텍스는 커널의 소유 -> 다른 프로세스에서 접근이 가능

- 핸들 테이블(커널 오브젝트와 이를 지칭하는 핸들값에 대한 정보를 담고 있는 테이블)

└ 각각 프로세스별로 독립적 -> 다른 프로세스로 핸들값 넘겨야함 -> 뮤텍스의 이름을 이용

- 운영체제 내에서 유일한 이름

 

HANDLE OpenMutex (

DWORD dwDesiredAccess,    // 뮤텍스로의 접근 권한, MUTEX_ALL_ACCESS

BOOL bInheritHandle,            // 핸들의 상속 유무

LPCTSTR lpName                  // 핸들의 커널 오브젝트 이름

);

- 다른 프로세스의 뮤텍스 핸들을 얻음

 

cf. WaitForSingleObject()의 WAIT_ABANDONED

- 뮤텍스는 뮤텍스를 획득하는 쓰레드와 반환하는 쓰레드가 같아야함

- 세마포어의 경우 세마포어를 획득하는 쓰레드와 반환하는 쓰레드가 달라도 됨

- 쓰레드A가 획득한 뮤텍스를 쓰레드B가 기다리고 있는데 반환하지 않고 쓰레드A가 종료 됐을 경우

운영체제에서 알아서 감지하여 쓰레드B가 뮤텍스를 획득하도록 하면서 WAIT_ABANDONED 반환 

- WAIT_ABANDONED 반환 자체는 오류가 아니지만 프로그램 코드에는 문제가 있음

- 예견하며 활용하기 보다 디버깅 관련해서 삽입하는 경우가 대부분

 

Event

- 이벤트 오브젝트 사용

- 자동 리셋 모드시 : 자동으로 Non-Signal 상태가 되므로 계속 쓰레드를 실행하려면 SetEvent()를 계속 해줘야 한다. ( PulseEvent 와 비슷(?) )

- 수동 리셋 모드시 : Non-Signal 상태를 설정 할 수 있으므로 계속 쓰레드를 실행 하다가 필요한 시점에만 Reset 시켜서 멈출 수 있다.

 

HANDLE CreateEvent(

LPSECURITY_ATTRIBUTES lpEventAttributes,    // 보안 속성

BOOL bManualReset,                                     // TRUE : 수동 리셋 모드, FALSE : 자동 리셋 모드

BOOL bInitialState,                                         // TRUE : Signaled 상태, FALSE : Non-Signaled 상태

LPCTSTR lpName                                           // 이벤트 오브젝트 이름, NULL : 이름 없음

);

- CloseHandle() : 이벤트 오브젝트 소멸

- 쓰레드가 WaitForSingleObject()를 호출하여 블로킹 상태 (Non-Signaled 상태)가 되고

   Signed 상태가 되어 빠져 나올 때

└ 이벤트 오브젝트가 Signaled 이면 : 수동 리셋 모드 -> ResetEvent()로 Non-Signal 변경

└ 이벤트 오브젝트가 Non-Signaled 이면 : 자동 리셋 모드

 

BOOL ResetEvent (        // 수동 리셋 모드시 이벤트를 Non-Signal 상태로 만듦

HANDLE hEvent

);

 

BOOL SetEvent (            // Non-SIgnal 상태의 이벤트를 Signal 상태로 만듦

HANDLE hEvent

);

 

 

BOOL OpenEvent (            // 다른 프로세스에서 수행되는 스레드의 경우

DWORD dwDesiredAccess,

BOOL bInherit,

PCTSTR pszName

);

 

'programing > Thread' 카테고리의 다른 글

쓰레드 풀  (0) 2017.02.23
Fiber  (0) 2015.06.09
[링크] 멀티쓰레드 프로그래밍이 왜이리 힘드나요?  (0) 2015.06.02
타이버 기반 동기화  (0) 2015.05.01
쓰레드  (0) 2015.04.30