728x90

유저모드 도기화 기법을 적용할 경우, 커널 모드로의 전환이 불필요 하기 때문에 성능상 이점을 얻을 수 있다. 그리고 커널 모드 동기화에 비해 활용하는 방법도 단순하다.

 

크리티컬 섹션(Critical Section) 기반의 동기화

크리티컬 섹션 기반의 동기화를 사용하려면 크리티컬 섹션 오브젝트를 만들고 초기화 해야한다.

크리티컬 섹션 오브젝트는 자료형 CRITICAL_SECTION의 변수이다.

선언 예시)

CRITICAL_SECTIN gCriticalSection;

 

크리티컬 섹션 오브젝트를 선언한 이후에는 다음 함수를 통해 초기화 과정을 거쳐야 한다.

void InitializeCriticalSection(

   LPCRITICAL_SECTION lpCriticalSection

);    // lpCriticalSection : 초기화 하고자 하는 크리티컬 섹션 오브젝트의 주소를 넘긴다.

위 함수로 초기화 과정을 거쳐야만 크리티컬 섹션 오브젝트는 사용가능한 상태가 된다.

 

크리티컬 섹션 오브젝트 초기화 과정

ex)

CRITICAL_SECTION * gCriticalSection;

 

int _tmain(int argc, TCHAR * argv[])

{

    ...

    InitializeCriticalSection(gCriticalSection);

    ...

}

 

크리티컬 섹션에 진입하고 빠져나오는데에 특별한 함수가 필요하다. 크리티컬 섹션 접근 동기화를 위한 함수들이다.

 

크리티컬 섹션 접근함수

void EnterCriticalSection(

   LPCRITICAL_SECTION lpCriticalSection

);

lpCriticalSection : 임계 영역에 진입하기 위해 필요한 크리티컬 섹션 오브젝트의 주소값이 인자로 사용된다. 다른 쓰레드에 의해 이 함수가 호출된 상태라면 호출된 함수는 블로킹 상태가 되고 다른 쓰레드가 반환하면 호출된 함수는 블로킹 상태를 빠져나온다. 위 함수를 호출한 쓰레드가 크리티컬 섹션 오브젝트를 획득했다고 표현한다.

 

크리티컬 섹션 빠져나오는 함수

void LeaveCriticalSection(

  LPCRITICAL_SECTION lpCriticalSection

);

lpCriticalSection : 위 함수는 임계영역을 빠져 나와서 호출하는 함수이다. 다른 쓰레드가 EnterCriticalSection 함수를 호출하고 블로킹 상태에 놓여있다면, LeaveCriticalSection 함수 호출을 통해 현재 쓰레드가 임계영역 접근을 반환하고 블로킹 상태에 놓여있던 다른 쓰레드가 블로킹 상태를 빠져나와 임계영역에 접근한다.

LeaveCriticalSection 함수가 호출이 완료되었을 때 이 함수를 호출한 쓰레드가 크리티컬섹션 오브젝트를 반환했다고 표현한다.

 

 위 두 함수 사용 형태

//임계 영역 진입을 위한 크리티컬 섹션 오브젝트 획득

EnterCriticalSection(&CriticalSection);

                ...

            임계영역

                ...

//크리티컬 섹션 오브젝트 반환(임계영역 빠져나옴)

LeaveCriticalSection(&CriticalSection);

 

임계영역이 결정되면 임계영역 진입 이전에 "EnterCriticalSection" 함수를 호출하고, 임계영역을 빠져나간 이후에 "LeaveCriticalSection"함수 호출로 크리티컬 섹션 오브젝트를 반환한다. 이러한 방법을 통해 임계영역에 한순간에 하나의 쓰레드만 실행할 수 있도록 구성하는 것이 크리티컬 섹션 동기화 기법의 핵심이다.

 

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

void DeleteCriticalSection(

    LPCRITICAL_SECTION lpCriticalSection

);

lpCriticalSection : 반환하고자 하는 크리티컬 섹션 오브젝트의 주소값을 인자로 전달한다.

위함수를 통해 크리티컬 섹션 오브젝트를 제거한다.

 

인터락 함수(Interlocked Family Of Function)기반의 동기화

인터락 함수는 내부적으로 한순간에 하나의 쓰레드에 의해서만 실행되도록 동기화 되어있다. 인터락 함수는 전역으로 선언된 변수 하나의 접근 방식을 동기화 하는 것에 특화 되어 있다.

 

인터락 함수

LONG InterlockedIncrement(

    LONG volatile* Addend

);

Addend : 값을 하나 증가시킬 32비트 변수의 주소값을 전달한다. 둘 이상의 쓰레드가 공유하는 메모리에 저장된 값을 이 함수를 통해 증가시킬 경우 동기화된 상태에서 접근하는 것과 동일한 안전성을 보장받을 수 있다.

 

LONG InterlockedDecrement(

  LONG volatile * Addend

);

Addend : 값을 하나 감소시킬 32비트 변수의 주속ㅄ을 인자로 전달한다. 둘 이상의 쓰레드가 공유하는 메모리에 저장된 값을 이 함수를 통해서 감소 시킬 경우, 동기화된 상태에서 접근하는 것과 동일한 안전성을 보장받을 수 있다.

 

InterlockedIncrement 함수와 InterlockedDecrement 함수는 원자적 접근(Atomic Access), 한순간에 하나의 쓰레드만 접근하는것을 보장해주는 함수이다. 크리티컬 섹션 동기화 기법도 내부적으로는 인터락 함수를 기반으로 구현되어있다. 위의 인터락 함수들도 유저모드 기반으로 동작하기 때문에 속도가 상당히 빠르다.

위 함수들을 통해 더 간결하게 안전한 쓰렏 형태를 만들 수 있다.

ex)

void IncreaseCount()

{

  //gTotalCount++;

  InterlockedIncrement(&gTotalCount+);

}

 

마이크로 소프트에서는 더 다양한 인터락 함수들을 제공한다.

원하는 수만큼 값을 증가시키거나 감소시키는 함수, 64비트 변수를 대상으로 연산하는 함수 등등

 

volatile 키워드

volatile 키워드의 의미는 크게 두가지가 있다.

1.최적화를 수행하지 마라

2.메모리에 직접 연산하라

 

1.최적화를 수행하지 마라

컴파일러는 코드를 컴파일 하는 과정에서 코드의 최적화를 수행한다.

ex)

int function(void)

{

  int a=10;

  a= 20;

  a= 30;

 cout << a;

}

--->

int Function(void)

{

     int a= 30;

     cout << a;

}

프로그램  실행결과가 동일하다는 관점에서 컴파일 과정에서의 코드 최적화가 이루어 질수도 있다.

이러한 최적화가 문제가되는 상황이 존재한다.

임베디드 시스템을 구성할 때 메모리 맵(Memory Map)디자인 이라고 하는 과정을 거치게 되는데, 이 과정에서 출력을 위해 사용되는 LCD나 소리를 내기 위해 사용되는 오디오 칩과 같은 하드웨어 장치에도 주소를 할당하게 된다. 즉 메모리 주소가 RAM과 같은 저장장치에만 할당되는 것이아니라, 하드웨어 장치에도 할당된다.

 

위와같은 상황에 최적화를 막기 위해 volatile 키워드를 사용한다.

ex)

int function(void)

{

   int volatile  * psound = 0x30000;

   ...

}

 

2."메모리에 직접 연산하라!"

ex)

int function(void)

{

   int * psound = 0x30000;

   SleepUntil(3, 35, 12);

   *psound = 2;

   ...

}

SleepUntil 이라는 함수를 만들어서 사용한 프로그램이다. 위 프로그램을 3시 35분 12초에 "미"음을 내는 프로그램이다.

3시35분 12초에 "* psound = 2"즉 0x30000에 데이터 2가 입력되면서 '미'음이 발생해야 한다. 그러나 성능향상을 위해 캐쉬메니저가 데이터를 캐쉬 메모리에 저장했다면 우리가 원하는 시점에 '미'음을 들을 수 없다.

(언젠가는 캐쉬에 저장된 데이터가 메모리에 저장되므로 소리는 발생할 것이다.)

 

위와같은 문제를 막기위해 volatile키어드를 사용할 수 있다. volatile로 선언되면 해당 데이터는 절대로 캐쉬되지 않는다. 바로 메모리에 직접 연산하게된다.

 

인터락 함수 인자들이 volatile로  선언되어 있다. 이는 함수 내부적으로 최적화를 수행하지 않으며, 해당 포인터가 가리키는 메모리영역을 캐쉬하지 않겠다는 것을 의미한다.

728x90

+ Recent posts