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;

}


Posted by system
l