2010년 3월 30일 화요일

MFC에서의 멀티쓰레드(Multithread)

MFC에서의 Multithread
 
OS는 구분하지 않지만 MFC는 사용자 편의를 위하여 두 가지 형태로 지원
 
1.     Worker thread
2.     User Interface thread
 
Worker thread
 
::AfxBeginThread() 함수를 이용
 
CWinThread* ::AfxBeginThread(
       AFX_THREADPROC pfnThreadProc,
       LPVOID pParam,
       int nPriority = THREAD_PRIORITY_NORMAL, // 기본적으로 주 Process와 동일
       UINT nStackSize = 0,
       DWORD dwCreateFlags = 0,                      // 0 또는 CREATE_SUSPENDED
       LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);
 
우선 Thread를 이용할 함수를 먼저 정의한다
 
UINT ThreadFunc(LPVOID pParam)
{
       int option = (int)pParam;
       …
}
 
만약 인자가 많은 경우에는
 
typedef struct tagPARAMS
{
       ...
} PARAMS;
 
와 같이 한 후에
 
PARAMS *pParams = new PARAMS;
CWinThread *pThread = ::AfxBeginThread(ThreadFunc, &pParams);
 
와 같이 하고
 
UINT ThreadFunc(LPVOID pParam)
{
       PARAMS *pThreadParams = (PARAMS *)pParam;
       ...
       delete pThreadParams;
 
       return 0;
}
 
와 같이 사용하면 된다.
 
Thread를 잠시 중지시키고 싶을 때는 주 Process에서
 
pThread->SuspendThread();
 
다시 돌리고 싶을 때는 주 Process에서
 
pThread->ResumeThread();
 
와 같이 하면 된다.(Thread 자신이 호출할 수는 없다.)
또는 경우에 따라서는
 
Sleep(2000);
 
과 같이 사용할 수도 있는데 이 경우는 제어권을 다른 Process에 넘겨 주게 된다.
 
Sleep(0);
 
와 같이 할 경우에는 우선 순위가 높거나 같은 Process에 넘겨 주고 우선 순위가 높거나
같은 Process가 없을 경우에는 아무 일도 생기지 않는다.
 
Thread를 종료시키고 싶을 때는 TerminateThread() 함수를 사용하면 되는데 이 경우 Thread 함수가
내부 정리를 하지 못할 수가 있기 때문에 다음과 같은 방법이 많이 사용된다.
 
static BOOL bContinue = TRUE;
CWinThread *pThread = ::AfxBeginThread(ThreadFunc, &bContinue);

 
UINT ThreadPrintNum(LPVOID pParam)
{
       BOOL *pbContinue = (BOOL *)pParam;
       while ( *pbContinue )
       {
             …
       }
       return 0;
}
 
와 같이 하고 bContinue 값을 FALSE로 하면 Thread 함수가 종료된다.
 
Thread가 완전히 종료된 것을 확신해야 하는 경우에는
 
if ( ::WaitForSingleObject(pThread->m_hThread, INFINITE) )
{
       // 이곳은쓰레드가확실히종료된상태임
}
 
와 같이 하면 된다. Thread가 죽어 버려서 먹통이 되는 경우까지 대비하려면
 
DWORD result;
result = ::WaitForSingleObject(pThread->m_hThread, 1000);   // 1초기다림
if ( result == WAIT_OBJECT_0 )
{
       // 이곳은쓰레드가확실히종료된상태임
}
else if ( result == WAIT_TIMEOUT )
{
       // 1초가지나도쓰레드가종료되지않은상태
}
 
이 방법을 사용해야 한다. 어떤 Thread가 현재 실행 중인지 알고 싶을 때는
 
if ( ::WaitForSingleObject(pThread->m_hThread, 0 ) == WAIT_TIMEOUT )
{
       // pThread 실행중
}
else
{
       // pThread가실행중이아님
}
 
와 같이 하면 된다.
 
User Interface Thread
 
User interface thread는 그 자체로 윈도우와 메시지 루프를 가지고 있다.
 
class CUIThread : public CWinThread
{
       DECLARE_DYNCREATE(CUIThread)
 
public:
       virtual BOOL InitInstance();
};
 
이 User interface thread는 독자의 윈도우도 가질 수 있다. 일반적으로 전용 Dialog를 띄워
Thread를 처리하는 경우가 많으므로 이 User Dialog를 CMyDialog라고 이름 지었다고 가정하면
 
IMPLEMENT_DYNCREATE(CUIThread, CWinThread)
 
BOOL CUIThread::InitInstance()
{
       m_pMainWnd = new CMyDialog;
       m_pMainWnd->ShowWindow(SW_SHOW);
       m_pMainWnd->UpdateWindow();
       return TRUE;
}
 
와 같이 CMyDialog를 Thread로 띄울 수 있다. 그 다음
 
CWinThread *pThread = ::AfxBeginThread(RUNTIME_CLASS(CUIThread));
 
와 같이 하면 MFC가 알아서 CUIThread를 생성해서 그 포인터를 pThread에 넘겨 준다.
 
아래 예제에는 CMyDialog를 띄우고 주 Process는 사용자의
입력을 기다린다. Dialog의 Design 및 생성은 별도로 이야기하지 않는다. 아래 예제를 사용하기 위해서는
CMyDialog를 만들고 ID를 IDD_MYDIALOG라고 가정하면 CMyDialog의 생성자에 다음과 같이 추가해야 제대로 동작한다.
 
CMyDialog::CMyDialog(CWnd* pParent /*=NULL*/)
       : CDialog(CMyDialog::IDD, pParent)
{
       Create(IDD_MYDIALOG, NULL);
}
 
이제 완전히 별도로 동작하는(Thread로 동작하는) 윈도우를 하나 가지는 것이다. 만약 이것을 Dialog가 아닌
FrameWnd라고 해도 거의 똑같다. 다만 위에서도 언급했듯이 Thread를 이용할 때는 Dialog가 더 일반적일 것이다.
Worker thread에 비해 훨씬 더 많은 기능을 하는 것을 알게 되었을 것이다.
 
나머지 것들은 위의 Worker Thread에 있는 내용과 동일하다.
 
만약 여러 개의 CUIThread 를 여러 개 만들려고 한다면
 
CWinThread *pThread[5];
for ( int i = 0; i < 5; i++ )
       m_pThread[i] = ::AfxBeginThread(RUNTIME_CLASS(CUIThread));
 
와 같이 하면 5개의 Thread가 생성된다.
 
Program Execution Priority(프로그램 실행 우선순위)
 
Thread는 0~31까지의 priority를 가질 수 있다.
 
프로그램의 priority는
 
BOOL SetPriorityClass(HANDLE hProcess, DWORD dwPriorityClass);
 
함수를 이용해서 조정할 수 있다. 첫 번째 인자 hProcess는 ::AfxGetInstanceHandle()로 얻으면 된다.
 
dwPriorityClass
Execution Priority
Description

DLE_PRIORITY_CLASS
CPU가 IDLE일 때만 사용 가능

ORMAL_PRIORITY_CLASS
보통

IGH_PRIORITY_CLASS
높은 우선 순위

EALTIME_PRIORITY_CLASS
최상위의 우선순위


Thread Execution Priority(쓰레드 실행 우선순위)
 
::AfxBeginThread() 함수의 nPriority를 통해서 설정하거나 CWinThread::SetThreadPriority 를 사용해 설정할 수 있다.
 
BOOL SetThreadPriority(HANDLE hThread, int nPriority);
 
nPriority
Execution Priority
Description

HREAD_PRIORITY_IDLE
REALTIME_PRIORITY_CLASS의 경우 16, 그 외에는 1

HREAD_PRIORITY_LOWEST
프로세스의 우선순위보다 2단계 낮은 우선순위를 가진다

HREAD_PRIORITY_BELOW_NORMAL
프로세스의 우선순위보다 1단계 낮은 우선순위를 가진다

HREAD_PRIORITY_NORMAL
프로세스의 우선순위가 같은 우선순위

HREAD_PRIORITY_ABOVE_NORMAL
프로세스의 우선순위보다 1단계 높은 우선순위를 가진다

HREAD_PRIORITY_HIGHEST
프로세스의 우선순위보다 2단계 높은 우선순위를 가진다

HREAD_PRIORITY_CRITICAL
REALTIME_PRIORITY_CLAS의 경우 31 그 외에는 16


프로그래머가 우선순위를 조정해도 Windows Scheduler가 상황에 맞게 조정하기 때문에 우선순위는
생각하고 조금 다를 수 있다.
 
Thread & Memory
 
각 Thread의 지역 변수는 모두 별도로 Stack을 만들고 Local Variable들을 관리하기 때문에 위의
 
CWinThread *pThread[5];
for ( int i = 0; i < 5; i++ )
       m_pThread[i] = ::AfxBeginThread(RUNTIME_CLASS(CUIThread));
 
와 같은 경우에도 각 Thread가 다른 Thread를 침범하는 일은 없다.
 
이 쯤에서 끝났나 싶겠지만… 아직 갈 길이 멀다.
Critical section, Mutex, Semaphore 같은 것들은 다음에…
 
Multithreading synchronization(멀티쓰레드의 동기화) => http://blog.naver.com/xtelite/50023359879
 
프로그램

Worker thread
 
#include "stdafx.h"
#include "console.h"
 
#include <iostream>
 
using namespace std;
 
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
 
// The one and only application object
CWinApp theApp;
 
using namespace std;
 
UINT ThreadPrintNum(LPVOID pParam);
 
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
       int nRetCode = 0;
 
       // initialize MFC and print and error on failure
       if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
       {
             _tprintf(_T("Fatal Error: MFC initialization failed\n"));
             return 1;
       }
 
       static BOOL bContinue = TRUE;
       CWinThread *pThread = ::AfxBeginThread(ThreadPrintNum, &bContinue);
 
       int count = 0;
       while ( count < 1000 )
       {
             count++;
       }
 
       Sleep(1000);
       pThread->SuspendThread();
       cout << "Thread suspended. Waiting for 2 seconds" << endl;
 
       Sleep(2000);
       cout << "Thread resumed" << endl;
       pThread->ResumeThread();
 
       cout << "Quit thread" << endl;
       bContinue = FALSE;
       Sleep(100);
 
       return nRetCode;
}
 
// 쓰레드함수
UINT ThreadPrintNum(LPVOID pParam)
{
       BOOL *pbContinue = (BOOL *)pParam;
       int count = 0;
       while ( *pbContinue )
       {
             count++;
       }
       cout << "Exit thread" << endl;
       return 0;
}
 
User interface thread
 
#include "stdafx.h"
#include "console.h"
 
#include "MyDialog.h"
 
#include <cstdlib>
 
using namespace std;
 
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
 
// The one and only application object
CWinApp theApp;
 
using namespace std;
 
class CUIThread : public CWinThread
{
       DECLARE_DYNCREATE(CUIThread)
 
public:
       virtual BOOL InitInstance();
};
 
IMPLEMENT_DYNCREATE(CUIThread, CWinThread)
 
BOOL CUIThread::InitInstance()
{
       m_pMainWnd = new CMyDialog;
       m_pMainWnd->ShowWindow(SW_SHOW);
       m_pMainWnd->UpdateWindow();
       return TRUE;
}
 
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
       int nRetCode = 0;
 
       // initialize MFC and print and error on failure
       if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
       {
             _tprintf(_T("Fatal Error: MFC initialization failed\n"));
             return 1;
       }
 
       CWinThread *pThread = ::AfxBeginThread(RUNTIME_CLASS(CUIThread));
      
       system("pause");
 
       return nRetCode;
}


출처 : "http://system.tistory.com/entry/멀티-쓰레딩"