메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

한빛랩스 - 지식에 가능성을 머지하다 / 강의 콘텐츠 무료로 수강하시고 피드백을 남겨주세요. ▶︎

IT/모바일

Thread 동기화 방식의 이해와 선택

한빛미디어

|

2012-11-21

|

by HANBIT

23,027

제공 : 한빛 네트워크
저자 : 임영규, 대한상공회의소 충북인력개발원 정보통신과 교수

프로그래밍을 시작해서 조금 심도가 높은 부분을 접근하다 보면 Thread(쓰레드)라는 것을 공부하게 된다. 쓰레드는 메인 프로그램에서 생성된 자식 프로세스로 메인 프로그램의 코드 영역과 데이터 영역을 그대로 사용한다. 프로그램에서 특정 자원에 대한 처리를 병행 처리하는 경우에 쓰레드를 만들어 사용하게 된다. 쓰레드의 개수는 개발 환경과 사용하는 운영체제에 의존적이므로 이에 대한 자료는 해당 문서를 탐독할 필요가 있다.

본 고에서는 쓰레드를 만들어 사용하는 경우, 쓰레드에서 사용하는 자원(메모리, 디바이스 드라이버 파일, DLL)에 대한 동기화 방법을 살펴보고자 한다. 정확한 동기화 방식의 사용은 프로그램의 품질을 높일 수 있다.

쓰레드 동기화 방법

아래의 MSDN 참고 문헌을 보면, 윈도우 기반에서 쓰레드를 동기화하는 방식을 선택하는 방법을 제시하고 있다. 알려진 바와 같이 이벤트와 세마포어는 사용자 수준에서 동기화된 자원을 엑세스하는 것이고, 뮤텍스와 크리티컬섹션은 응용 프로그램 수준에서 동기화된 자원을 엑세스하는 방법이다. 정리하면, 응용 프로그램의 개수에 따라 자원 관리 개체가 사용자인지, 운영체제인지 결정되는 것이다.
The multithreaded classes provided with MFC fall into two categories: synchronization objects (CSyncObject, CSemaphore, CMutex, CCriticalSection, and CEvent) and synchronization access objects (CMultiLock and CSingleLock).
Synchronization classes are used when access to a resource must be controlled to ensure integrity of the resource. Synchronization access classes are used to gain access to these controlled resources. This topic describes when to use each class.
To determine which synchronization class you should use, ask the following series of questions:
1. Does the application have to wait for something to happen before it can access the resource (for example, data must be received from a communications port before it can be written to a file)?
If yes, use CEvent.
2. Can more than one thread within the same application access this resource at one time (for example, your application allows up to five windows with views on the same document)?
If yes, use CSemaphore.
3. Can more than one application use this resource (for example, the resource is in a DLL)?
If yes, use CMutex.
If no, use CCriticalSection.
CSyncObject is never used directly. It is the base class for the other four synchronization classes.
필요한 경우, 사용자는 프로그래밍을 할 때 쓰레드를 만들게 되는데, 이 경우 자원에 대한 동기화 방식에 대해 충분한 이해를 하지 않은 상태에서 위에서 제시한 각각의 경우 중 하나를 선택하게 된다. 이것은 응용 프로그램의 성능에 충분한 영향을 미치게 된다. 하나의 응용 프로그램 내에서 여러 개의 쓰레드를 만들어 각 자원 엑세스에 대한 접근을 하기 위해 크리티컬섹션이나 뮤택스를 사용하게 되면, 운영체제를 통한 문맥교환이 이루어 지기 때문에 그만큼 쓰레드 제어함수에서 메인 프로그램으로의 전환이 느려지게 된다.

반대로 여러 개의 응용 프로그램에서 각각의 쓰레드를 만들어 사용하는 경우에, 이벤트나 세마포어를 사용하여 자원에 대한 엑세스를 수행하게 되면, 운영체제가 프로세스 스케쥴링을 하면서 자원 엑세스에 대한 충돌이 발생하게 되어 예기치 못한 결과를 얻을 수 있다. 따라서 이에 대한 충분한 이해가 있어야 정상적인 프로그램 수행 결과를 얻을 수 있다.

자원에 대한 쉬운 설명으로, 하나의 응용 프로그램에서 쓰레드 기반의 자원을 사용하는 간단한 설명은 전역변수를 들 수 있다. 다수의 응용 프로그램에서 쓰레드에서 동기화하는 자원은 디바이스 장치(파일)를 들 수 있다. 일반적으로 전역 변수를 이용하여 쓰레드 동기화에 방법에 대한 설명을 많이 해 놓은 것을 기반으로 지식을 쌓다 보면, 실제 디바이스 제어를 하는 경우에 위에서 설명한 바와 같은 오류를 범할 수 있다.

운영체제는 파일 시스템이다. 따라서 운영체제는 실제 물리적인 장치(비디오 카드, 사운드 카드, 이더넷 카드, 하드 디스크, 프린트 등)를 인식하지 못한다. 이들을 제어하기 위해서는 디바이스 드라이버, 즉 디바이스 드라이버 제어 파일을 통하여 이들을 제어한다. 그러므로 이러한 디바이스 의존적 프로그래밍을 하는 경우에 쓰레드를 사용하는 경우에는 자원 동기화에 대한 정확한 이해를 필요로 한다.
CWinThread *th;

  // TODO: Add your control notification handler code here
  th = AfxBeginThread(ThreadProc,this,0, 0, CREATE_SUSPENDED,0);
쓰레드를 생성하는 방법을 보면, 먼저 쓰레드 개체의 인스턴스를 정의하고, 쓰레드 생성 시 대기 상태로 쓰레드를 생성해야 한다. 생성과 동시에 바로 쓰레드 제어함수로 제어권을 보내게 되면 정상적인 쓰레드 처리가 불가능한 에러가 발생할 수 있다. 그리고 쓰레드를 종료하기 위해서는 대기 상태로 보내는 것이 아니라 완전히 메모리에서 제거해 주어야 한다. 일반적으로 쓰레드를 사용하는 경우 대기 상태로 변경하는 실수를 흔히 많이 사용하는 것을 볼 수 있다. 그리고 쓰레드를 사용하게 되는 시점에서 해당 쓰레드를 재시작시키는 방법을 사용하여야 한다.
  // TODO: Add your control notification handler code here
  th->ResumeThread();
쓰레드 제어 코드를 다음 예시와 같이 제시한다. 이 예제에서는 뮤텍스를 이용한 쓰레드 공유방법을 제시하고 있다.
CMutex m_ThreadMutex;
UINT __cdecl ThreadProc( LPVOID pParam )
{
  CSingleLock singleLock(&m_ThreadMutex);
  CTestMFCDlg* dlg = (CTestMFCDlg *)pParam;
  static int i=1000;
  do 
  {
    // 쓰레드는 메인 프로그램과 별개의 프로세스 ID를 가지고 실행됨으로
    // SetDlgItemText 함수를 사용함, UpdateData()함수를 사용하려면 사용자 메시지를 이용해야 함
    SetDlgItemText(dlg->m_hWnd, IDC_STATIC, L"Lock, 	m_iValue & TextEdit");

    Sleep(1000);
    WaitForSingleObject(th, INFINITE);  

    singleLock.Lock();    // 공유자원을 사용함
    dlg->m_iValue = 100;
    if (endThread == TRUE)
    {
      SetDlgItemInt(dlg->m_hWnd, IDC_EDIT1, 0, 0);
      AfxEndThread(0, TRUE);  // 쓰레드 종료
      break;
    }
    else
      SetDlgItemInt(dlg->m_hWnd, IDC_EDIT1, i++, 0);

    Sleep(2000);
    SetDlgItemText(dlg->m_hWnd, IDC_STATIC, L"UnLock, 	m_iValue & TextEdit ");
    singleLock.Unlock();   // 공유자원 반납

    Sleep(1000);
  } while(TRUE)

  return 0;
}
그림과 같이 쓰레드를 실행하게 되면 m_ivalue 값을 증가시키는 자원(전역변수, 에디트 박스)을 해당 쓰레드가 락을 건 후 다른 쓰레드로 하여금 접근하지 못하게 하고, m_ivalue 값을 증가시킨 후 에디트 박스에 표시한다.



쓰레드에세 자원에 동기화를 해제한 경우에 다른 쓰레드(메인 쓰레드를 포함하는 다른 쓰레드)에서 전역변수와 에디트 박스에 대한 접근이 가능한다. 메인 쓰레드란 실행중인 메인 프로그램이 된다.



쓰레드를 종료하여 m_iValue 값을 0으로 초기화시킨 것이다.



쓰레드가 실행되었을 때는 그림과 같이 메모리 점유가 늘어나는 것을 볼 수 있다.



쓰레드가 종료 되었을 때 그림과 같이 메모리가 줄어 드는 것을 볼 수 있다.



이제 Dll을 다수의 쓰레드에서 사용하는 경우에 대하여 알아보자. 아래 예에서 제시한 바와 같이 다수의 응용 프로그램에서 쓰레드를 사용하여 특정 Dll을 임계영역에 두었다고 가정하자. 이 Dll 내의 함수에 엑세스를 하고자 한다면, 크리티컬섹션이나 뮤텍스를 이용하여 접근하여야 한다고 설명하였다. 일련의 과정을 기술해 보면 다음과 같다. 단, Dll 내에 int Mysum(int x, int y)라는 함수가 있고 Dll 이름은 Calc.dll이라 가정한다. 먼저 각 응용 프로그램 내의 쓰레드 함수는 다음의 과정을 통하여 엑세스 가능하다.
HINSTANCE hDll;
CMutex m_ThreadMutex;

Thread_Porc()
{
  CSingleLock singleLock(&m_ThreadMutex); // 뮤텍스 생성

  WaitForSingleObject(th, INFINITE); // 먼저 실행된 쓰레드의 종료를 기다림

  singleLock.Lock();    // 공유자원을 사용함

  typedef int (*FuncMySum) (int varx, int var y); // Dll 내의 함수원형
  FuncMySum lpCalc;
  Int SumOfAdd;

  hDll = LoadLibrary(Calc.dll");  // Dll 로딩
  lpCalc = (FuncMySum) GetProcAddress(hDll, "sum");  // sum 함수의 포인터를 지정함

  SumOfAdd =lpCalc(5,3);  // Dll 내의 sum 함수 호출
  TRACE("%d + %d = %d
", 5, 3, SumOfAdd);

  FreeLibrary(hDll);   // Dll 언로딩

  singleLock.Unlock();   // 공유자원 반납

  return 0;
}
쓰레드에서 Dll을 사용하는 경우, 가급적 간접 로딩(Explicit) 방식으로 필요한 Dll만 메모리에 로딩을 하고, 다 사용한 다음에서 이 Dll을 언로딩하는 방법을 사용하는 것이, 다수의 응용 프로그램 내에서 각각의 쓰레드 경쟁을 피할 수 있으며, 예기치 못한 에러로부터 정상적인 프로그램의 실행을 보장 받을 수 있다. 그리고 필요하다면, 각 쓰레드의 실행 시간을 검사해 봄으로써 실제 쓰레드 동기화에 얼마나 많은 시스템 클럭이 필요한지를 확인 해 볼 수 있다.

결론

쓰레드 동기화에 대한 방법에 대하여 알아 보고, 각각의 경우에 대하여 적절한 쓰레드 동기화 벙법을 선택할 것인지는 사용자의 몫이다. 쓰레드 동기화 방법에 대한 정확한 이해가 없이 접근하게 되면 잘 만들어진 응용프로그램도 성능을 고려한 작성법에는 조금 부족한 면을 가지게 된다. 따라서 정확한 쓰레드 동기화는 프로그램의 품질을 높일 수 있을 뿐 아니라, 프로그램의 오류로 인한 디버깅 시간, 개선 작업(버전 업)에 대한 상당한 시간을 절약할 수 있다. 위에서 제사한 내용이 쓰레드 동기화를 이해 할 수 있는 도움이 되길 바란다.
TAG :
댓글 입력
자료실

최근 본 상품0