multithread programming(1)
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 |
IDLE_PRIORITY_CLASS |
CPU가 IDLE일 때만 사용 가능 |
NORMAL_PRIORITY_CLASS |
보통 |
HIGH_PRIORITY_CLASS |
높은 우선 순위 |
REALTIME_PRIORITY_CLASS |
최상위의 우선순위 |
Thread Execution Priority(쓰레드 실행 우선순위)
::AfxBeginThread() 함수의 nPriority를 통해서 설정하거나 CWinThread::SetThreadPriority 를 사용해 설정할 수 있다.
BOOL SetThreadPriority(HANDLE hThread, int nPriority);
nPriority
Execution Priority |
Description |
THREAD_PRIORITY_IDLE |
REALTIME_PRIORITY_CLASS의 경우 16, 그 외에는 1 |
THREAD_PRIORITY_LOWEST |
프로세스의 우선순위보다 2단계 낮은 우선순위를 가진다 |
THREAD_PRIORITY_BELOW_NORMAL |
프로세스의 우선순위보다 1단계 낮은 우선순위를 가진다 |
THREAD_PRIORITY_NORMAL |
프로세스의 우선순위가 같은 우선순위 |
THREAD_PRIORITY_ABOVE_NORMAL |
프로세스의 우선순위보다 1단계 높은 우선순위를 가진다 |
THREAD_PRIORITY_HIGHEST |
프로세스의 우선순위보다 2단계 높은 우선순위를 가진다 |
THREAD_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;
}
[출처] Multi-thread programming(멀티 쓰레딩)|작성자 엘리트