Windows_/Windows32_API

Windows :: 유저모드 스레드 동기화

sosal 2014. 7. 24. 16:39
반응형

/*

 * http://sosal.kr/

 * made by so_Sal

 */



동기화에 대해서 기본적인 지식이 없다면..
링크를 잠깐 확인하고 오세요 :: LINK_

임계영역 :: 둘이상의 스레드가 동시에 접근하는 위험요소를 지니는 코드 블럭
                 쉽게 동기화는 임계영역 위험요소에 의해 발생될 문제를 제거하는 것이라 할 수 있다.

임계영역은 어떤 메모리를 말하는 것이 아니라, 위험요소를 지닌 코드입니다.
이 포스트에서 살펴볼 유저모드의 동기화 입니다. (다음 포스트에 커널모드로 연재합니다.)



이번장에서 살펴볼 내용
  ::  1. 크리티컬 섹션 기반 동기화 + 예제 
       :: void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
       :: void EnterCriticalSection( LPCRITICAL_SECTION lpCriticalSection);
       :: void LeaveCriticalSection( LPCRITICAL_SECTION lpCriticalSection);
       :: void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);



크리티컬 섹션 기반 동기화
 
Critical Section 기반 동기화
 ::  한글로 번역하면, 임계영역 이란 뜻을 가집니다.
     임계영역만을 동기화시키는 동기화 기법은 아닙니다.
     단지 임계영역을 동기화하는데 최상적인 모델이라는 뜻입니다.

동기화 기법은 화장실 열쇠에 종종 비유하곤 합니다.
화장실에 들어갈 수 있는 인원 1명. 열쇠는 1개. 사람은 n명.

1. 사람들이 화장실에 가기를 원한다.
2. 밖에 걸린 열쇠(단 하나의) 를 획득한 사람이 화장실에 먼저 들어간다.
3. 볼일을 본 후 열쇠를 다시 걸어놓는다.
4. 다른이들이 열쇠를 획득하고 위를 반복한다.

이것이 바로 Critical Section 기반 동기화의 예입니다.



다음은 각각에 비유할 수 있다.
CRITICAL_SECTION 구조체 :: key
스레드 :: 사람
임계영역 :: 화장실
임계영역 진입 EnterCriticalSection()   :: 화장실로의 입실
임계영역 퇴장 LeaveCriticalSection()  :: 화장실에서 퇴장






void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
CRITICAL_SECTION 구조체를 하나 선언하게 되면
InitializeCriticalSection() 함수를 이용하여 구조체를 초기화 하셔야 합니다.
보통 이런 초기화 함수가 따로 있는 구조체에서는
초기화 함수가 단순한 초기화만이 아닌, 동기화 작업에 필요한 최소한의 기본작업을
내부적으로 처리하도록 요청(또는 직접)하게 됩니다.
초기화 함수를 이용해 구조체를 초기화 하는것을 잊지마세요.




void
EnterCriticalSection( LPCRITICAL_SECTION lpCriticalSection)

이 함수가 실행 (혹은 성공) 했다는 것은,
스레드가 임계영역 (화장실)로 진입하기 위해 필요한
Critical Section 구조체 (열쇠)를 획득하여
화장실로 들어감을 뜻합니다.

만약 다른 스레드가 같은 구조체를 이용하여 위 함수로 호출했을 시,
호출이 끝날때 까지 함수는 block 됩니다.
위 함수 호출이 성공됨을 Critical Section object의 획득이라 말합니다.





void
LeaveCriticalSection( LPCRITICAL_SECTION lpCriticalSection)

이 함수가 실행 (혹은 성공) 했다는 것은,
스레드가 임계영역 (화장실)에서 퇴장하면서
Critical Section 구조체 (열쇠)를 열쇠걸이에 다시 놔둠을 뜻합니다.

임계영역에는 오직 한 스레드만이 진입하기 때문에,
EnterCriticalSection() 함수처럼 경쟁할 이유가 없겠죠?





정리하자면...

 EnterCriticalSection(); //열쇠 획득                  <임계영역> //시작

    넓게잡을수록 안정적. 열쇠 반환이 너무 잦으면, 성능이 안좋아지기 때문.
    하지만, 필요이상으로 범위를 넓게 만들면 다른 스레드의 실행에 문제발생.
    (오줌을 바지에 찌릴수가 있습니다. ㅠㅠ)
    임계영역을 구성할 때에는 최소한의 영역으로 적당~히 필요한 부분만 감싸야 합니다.

 LeaveCriticalSection(&cs);  //열쇠 반환           </임계영역> //끝






void
DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
InitializeCriticalSection() 함수로 구조체를 초기화 하게 되면,
동기화 작업에 필요한 최소한의 기본작업을 내부적으로 처리하도록
작업을 요청한다고 했는데, 그때 생성된 리소스들을 위 함수로 반환해야 합니다.





================== Critical Section 동기화 기법 예제 ==================

#include<stdio.h>
#include<windows.h>
#include<process.h>
#include<tchar.h>

#define MAX_THREADS  10

DWORD CS_TotalCount = 0;   // Critical Section 기반 동기화가 이루어질 변수
DWORD TotalCount = 0;         // Critical Section 기반 동기화가 이뤄지지 않을 변수
                                          // 위 두 변수가 동기화의 비교 대상입니다.

CRITICAL_SECTION
cs;    // 동기화로 사용될 구조체 (화장실 열쇠)

void IncreaseCount(){
   TotalCount++;                           // 임계영역 밖에있는 변수
   EnterCriticalSection(&cs);   // 화장실 진입


   CS_TotalCount++;                    //임계영역 안에있는 변수
   LeaveCriticalSection
(&cs);  // 화장실 퇴장
}

unsigned int WINAPI ThreadProc( LPVOID lpParam){
    for(DWORD i=0; i<10000 ; i++){    // 각 10개의 스레드는 전역변수 2개를
        IncreaseCount();                 // 10000 만큼 증가시킵니다.
}                                                // 원래 변수 모두가 10만이라는 결과를 도출해야 하는데
                                                 // 결과는 그렇지 않죠.

 return 0;
}

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

    DWORD dwThreadId[MAX_THREADS];
    HANDLE hThread[MAX_THREADS];

    InitializeCriticalSection(&cs);        // CriticalSection 구조체 초기화

    for(DWORD i=0;i<MAX_THREADS;i++){
        hThread[i] = (HANDLE)

         _beginthreadex(                                   // 스레드 생성

         NULL,0,ThreadProc,NULL,

         CREATE_SUSPENDED,                    // Suspend Count 1로 시작하기 때문에

         (unsigned *)&dwThreadId[i]);               // 아래 ResumeThread()가 호출되기 전까지 실행x

     if(hThread[i] == NULL){
         _tprintf( _T("Thread creation fault ! \n"));

         return -1;

     }

 }

 for(DWORD i=0; i<MAX_THREADS ; i++)
     ResumeThread(hThread[i]);                   //스레드 생성하는데 시간이 많이 소비되기 때문에
                                                              //최대한 시간을 맞춰주기 위함

    WaitForMultipleObjects(
        MAX_THREADS,hThread,TRUE,INFINITE);

        _tprintf( _T("   Critical_section total count : %d\n"), CS_TotalCount); // 동기화된 변수

        _tprintf( _T("   non critical_section total count : %d\n   "), TotalCount); //그렇지 않은 변수

        for(DWORD i=0;i<MAX_THREADS ; i++)

        CloseHandle(hThread[i]);

 
    DeleteCriticalSection(&cs);

    return 0;
}


================== Critical Section 동기화 기법 예제 ==================