본문 바로가기

3.구현/VC++

[MFC]멀티쓰레드 Multi Thread

출처 : Visual C++ 6 완벽가이드 - 김용성

쓰레드 생성방법

1. Worker thread

2. User interface thread

1. Worker thread

이는 단일 작업을 수행하기 위해서 사용. 단일 함수로 구성됨.

int nNumber = 100;
CWinThread *pThread = ::AfxBeginThread(ThreadFunc, &nNumber);

ThreadFunc은 쓰레드가 작업할 함수이다. nNumber가 pParam으로 넘겨지는데 주의할 것은 넘어온 형이 LPVOID이기 때문에 원래 형으로 변환시켜야함.

UINT ThreadFunc(LPVOID pParam)
{
   UINT nIteration = (UINT) pParam;
   for(UINT i=0; i<nIteration; i++) {
      //수행할 작업
   }
}

여러 값을 넘겨주는 방법

typedef struct tagTREADPARAMS {
   // 필요한 변수들 선언
} THREADPARAMS;

THREADPARAMS *pThreadParams = new THREADPARAMS;
CWinThread *pThread = ::AfxBeginThread(ThreadFunc, pThreadParams);

이것 역시 형변환이 필요

THREADPARAMS *pThreadParams = (THREADPARAMS*)pParam;

CWindThread* ::AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );
  • pfnThreadProc : 작업할 함수명
  • pParam : 쓰레드 함수로 넘겨지는 인자
  • nPriority : 실행 우선순위. 기본값은 주 프로세스와 같음.
  • nStackSize : 사용할 스택 크기. 0은 최도 1MB까지 할당 가능
  • dwCreateFlags : 0은 바로 실행, CREATE_SUSPENDED로 하면 실행하지 않고 일시 정지하고 ResumeThread()로 실행
  • lpSecurityAttrs : SECURITY_ATTRIBUTES 구조체 포인터 사용해서 보안에 대한 설정.

2. User interface thread

CWinThread을 상속해서 정의하는 것으로 자체윈도우와 메시지 루프를 가지고 독립적으로 수행.

class CUIThread : public CWinThread
{
   DECLARE_DYNACREATE(CUIThread)

public:
   virtual BOOL InitInstance();
};

IMPLEMENT_DYNACREATE(CUIThread, CWinThread)

BOOL CUIThread::IniInstance()
{
   m_pMainWnd = new CUIThreadWnd;
   m_pMainWnd->ShowWindow(SH_SHOW);
   m_pMainWnd->UpdateWindow();
   return TRUE;
}

class CUIThreadWnd : public CFrameWnd
{
public:
   CUIThreadWnd();

protected:
   afx_msg void OnLButtonDown(UINT, CPoint);
   DECLARE_MESSAGE_MAP()
};

BEGIN_MESSAGE_MAP(CUIThreadWnd, CFrameWnd)
   ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()

CUIThreadWnd::CUIThreadWnd()
{
   Create(NULL, "사용자 인터페이스 쓰레드");
}

void CUIThreadWnd::OnLButtonDown(UINT nFlags, CPoint point)
{
   // 마우스 왼쪽 버튼 눌렀을 때 처리
   PostMessage(WM_CLOSE, 0, 0);
}

실제 수행도 AfxBeginThread()를 사용

CWinThread *pThread = ::AfxBeginThread(RUNTIME_CLASS(CUIThread));

3. 쓰레드 제어

  • 쓰레드 일시정지 : pThread->ResumeThread()

  • 쓰레드 휴식 : ::Sleep(1000); // 이는 쓰레드 안에서 실행

    • Sleep(0); //이는 자신 CPU사용권 넘김
  • 쓰레드 종료 :

    • User inface thread는 자제적으로 메시지 처리되기에 WM_QUIT가 발생하면 자동적으로 종료

    • Worker thread는 강제적으로 ::TerminateThread()으로 종료할 수 있으나 좋은 방법은 아니고 자체적으로 종료할 수 있게함

      예를 들어) pContinue를 두어 프로세스 변수하나를 받아서 서버에서 pContinue을 조절하여 쓰레드가 계속 실행할지 종료할지 제어한다. 이때 프로세스는 다음 같이 하여 모든 쓰레드 종료시까지 대기한다.

if(::WaitForSingleObject(pThread->m_hThread, INFINITE))
{
   // 쓰레드 종료후 할 작업
}

INFINITE는 무한대로 기다리는 것이기에 숫자로 일정 시간 대기형태

DWORD dwRetCode;
dwRetCode=::WaitForSingleObject(pThread->m_hThread, 2000);
if(dwRetCode == WAIT_OBJECT_0) {
   // 쓰레드 종료후 할 작업
} else if(dwRetCode == WAIT_TIMEOUT) {
   // 2초 시간 초과후 에러 처리
}

4. 쓰레드 우선순위

쓰레드 우선순위는 0~31단계를 가지며 ::SetPriorityClass() 사용

BOOL SetPriorityClass( HANDLE hProcess, DWORD dwPriorityCLass)

hProcess는 AfxGetInstanceHandle()를 통해 얻은 프로세스 핸들러
dwPriorityClass는 다음 인자중에서 하나 사용

  • IDLE_PRIORITY_CLASS
  • NORMAL_PRIORITY_CLASS
  • HIGH_PRIORITY_CLASS
  • REALTIME_PRIORITY_CLASS

5. 쓰레드의 메모리 범위

쓰레드는 주 프로세스 전역 함수를 공유
쓰레드 지역변수는 각각 스택에 따로 생성

6. 쓰레드 사용시 주의점

서로 다른 스택을 갖고 있기에 윈도우 핸들러사용시 주의.

CWnd *pWnd = (Cwnd *) pParam;
Cwnd *pWnd = pWnd->GetParentFrame();

이는 GetParentFrame()이 쓰레드 용이 아니기에 에러 발생
C Runtime Library : 쓰레드용을 사용

다음으로 쓰레드 동기화에 대한 문제

동기화 해결 방법

1. Events
2. Critical Sections
3. Mutexes
4. Semaphores
5. CMultiLock (MFC)

1. Events

메시지를 통해서 상대방을 제어하는 형태

CEvent g_Event(FALSE, TRUE);
UINT Thread Func(LPVOID pParam)
{
   while(TRUE) {
      g_Event.lock();
      // 이벤트 발생시 처리할 작업
   }
}

다른 쓰레드나 프로세스에서 아래와 같이 하면 g_Event.lock() 다음이 처리된다.

g_Event.SetEvent();
g_Event.ResetEvent();

2. Critical Section

동일한 자원을 Critical Section에 두어 동시에 접근하지 못하도록 하는 방법

CCriticalSection g_CriticalSection; // 전역함수로 선언

실제 사용예)

// 쓰레드 A
g_CriticalSection.Lock(); // 공유자원 묶음
// 쓰레드 A작업
g_CriticalSection.Unlock(); // 공유자원 해제

//쓰레드 B
g_CriticalSection.Lock(); // 공유자원 묶음
// 쓰레드 B작업
g_CriticalSection.Unlock(); // 공유자원 해제

위의 쓰레드 A와 B는 공유자원에 대해서 동시접근이 불가능

3. Mutex

CriticalSection과 같은 방법이지만 CriticalSection은 같은 프로세스 내의 쓰레드 간이라면 Mutext는 여러 프로세스에서 사용가능.
사용법도 같다.

4. Semaphore

특정 영역에 접근 가능한 개수를 설정할 수 있다. 이때 리소스 카운터를 두어서 접근 개수를 기억한다.

CSemaphore g_Semaphore(2, 2); // 첫번째 인자는 카운터 초기값, 두번째 인자는 최대 카운터 값이다.
g_semaphore.Lock();
// 이 부분 공유자원은 최대 2개의 쓰레드가 동시 사용가능
g_semaphore.Unlock();

5. Multi Lock

앞 에 CEvent, CCriticalSection, CMutex, CSemaphore 들은 모두 Lock()를 가지고 있다. 이런 Lock() 대신에 CMultiLock의 Lock()을 사용할 수 있다. 이를 이용함으로서 동기화 객체의 몇 개의 상태로 조합 가능.

CEvent g_Event[4]; // 총 4개의 전역 이벤트

// 4개의 이벤트가 모두 발생해야 Lock이 해제
CMultiLock multiLock(g_Event, 4);
multiLock.Lock();
// 4개의 이벤트 중 하나만 발생해도 Lock이 해제
CMutliLock multiLock(g_Event, 4);
multiLock.lock(INFINITE, 4);
반응형