API/MFC

Thread Programming

by Naya posted Aug 02, 2012
?

단축키

Prev이전 문서

Next다음 문서

ESC닫기

크게 작게 위로 아래로 게시글 수정 내역 댓글로 가기 인쇄
2. 쓰레드 프로그래밍

 

    1) Win32 API를 이용한 쓰레드 프로그래밍

       - 쓰레드 생성

HANDLE CreateThread(
     LPSECURITY_ATTRIBUTES lpThreadAttributes,
     SIZE_T dwStackSize,
     LPTHREAD_START_ROUTINE lpStartAddress,
     LPVOID lpParameter,
     DWORD dwCreationFlags,
     LPDWORD lpThreadId
);

 

    <인자 설명>

         lpThreadAttributes : 커널 오브젝트의 보안 속성. NULL을 주로 지정

         dwStackSize : 쓰레드의 스택(TLS:Thread Local Storage)크기를 지정.

                    NULL 지정은 기본값

         lpStartAddress : 커널 오브젝트가 실행할 함수에 대한 포인터

              여기에 들어갈 함수의 형식은 다음과 같이 정해져 있다.

              DWORD WINAPI 함수명 (VOID *인자)

         lpParameter : 쓰레드 함수에 전달할 데이터에 대한 포인터.

              넘겨 받은 후 원래의 타입으로 형변환 후 사용한다.

         dwCreationFlags : 시작시 스레드 상태에 대한 플래그

              . NULL : 커널 객체 생성과 동시에 쓰레드 함수 시작

              . CREATE_SUSPENDED : ResumeThread 호출전까지 대기상태에 있음.

        lpThreadId : 스레드 ID값을 넘겨 받는다. Win95계열에서는 NULL을 지정할 수 없다.

            쓰레드 ID는 시스템 전체를 통해 유일한 값이므로 이것을 많이 이용하게 된다.

            특히 쓰레드에 메시지를 전달하는 PostThreadMessage()함수는 쓰레드ID를 쓴다.

 

 


       - 쓰레드 종료

         ① 쓰레드 함수를 리턴 한다. => 자체 종료

         ② 쓰레드 함수 자체에서 ExitThread 함수를 호출한다.  => 자체 종료

         ③ 외부 쓰레드(메인 함수 포함)에서 Terminate 쓰레드를 호출한다.(비 권장)

         * 메인 쓰레드가 종료하면 서브 쓰레드들도 모두 종료한다.

 

 


       - 쓰레드의 상태 파악

         BOOL GetExitCodeThread (<쓰레드 핸들>, <종료코드를 담을 주소-DWORD>);

         종료코드 : STILL_ACTIVE (실행중)

                    ExitThread시 공통으로 들어가는 인자값

                    쓰레드 함수가 리턴한 값

                    쓰레드 종료 과정에서 발생한 예외값


       - Win32 API 쓰레드 프로그래밍시 주의 점

         . C런타임 라이브러리 함수를 혼용하면 안 된다. (쓰레드 Safe하지 않음)

         . Win32 API함수들만 이용하도록 한다.

 

 

 

    2) C런타임 라이브러리를 이용한 쓰레드 프로그래밍

       : C런타임 라이브러리는 원래 쓰레드 safe하지 않기 때문에 쓰레드 프로그래밍시에 사용을

         권하지 않는다. 그러나 VC++에서 런타임라이브러리 지정시 다중쓰레드 옵션을 주면

         사용할 수 있다.

       (process.h 인크루드)


       - 쓰레드의 생성

uintptr_t _beginthread(
    void( __cdecl *start_address )( void * ),
    unsigned stack_size,
    void *arglist
 );

 

         <인자 설명>

            void( __cdecl *start_address )( void * ) : 쓰레드 함수 (형식에 주목)

            unsigned stack_size,     : 스택의 크기 (0지정하면 기본 크기)

            void *arglist              : 함수에 전달된 인자 시작 주소


uintptr_t _beginthreadex(
    void *security,           // 보안 속성
    unsigned stack_size,     // 스택 크기
    unsigned ( __stdcall *start_address )( void * ),   // 쓰레드 함수(형식에 주목)
    void *arglist,             // 스레드 함수 인자
    unsigned initflag,         // 실행상태 지정
    unsigned *thrdaddr       // 쓰레드 ID
 );

 

       - 쓰레드 종료

         => EndThread함수나 return를 쓰지 않고 반드시 아래 함수를 사용해야 한다.

         void _endthread();

         void _endthreadex(<종료코드>);

 

       - 실행 함수 형식

            unsigned  __stdcall start_address ( void *p );

 

       - 특징

         : CreateThread 함수들을 쓰는 것보다 이들 함수를 사용하면 C런타임 라이브러리들을

         사용할 수 있으므로 편리하다. WinAPI 함수도 사용가능.

          . C++ 클래스의 메소드를 쓰레드로 실행 하려면 static 멤버이어야 한다.

            => 이 경우 객체의 일반멤버들의 사용이 필요하므로 자신에 대한 포인터를 넘기는

              것이 일반적이다.

         ex) // 생성자에서 아래 코드를 실행

            m_hThread = (void*)_beginthread(NULL,0,Func,this,0,&addr);

            static unsigend __stdcall Func(void *p)

            {     CMyThread *c = (CMyThread*) p; .....              }

 

 

 


    3) MFC를 이용한 쓰레드 프로그래밍

       : CWinThread 클래스를 사용한다.

       - 특징

         . CWinThread 클래스에는 자체적으로 메시지 펌프가 존재하기 때문에 이벤트를 발생시

            켜 메시지를 처리 할 수 있다.

         . CWinApp가 CWinThread를 상속받은 것이다.

         . CWinThread 용도

            ① 작업자 쓰레드 : 사용자의 입력이 필요없는 작업시

            ② UI 쓰레드 : 사용자의 입력을 받거나 이벤트를 받아 실행할 목적의 쓰레드


       가) Worker Thread만들기

         : 특정 함수를 이용해서 CWinThread를 생성하는 방법.


         - 쓰레드 생성

CWinThread* AfxBeginThread(
      AFX_THREADPROC pfnThreadProc,
      LPVOID pParam,
      int nPriority = THREAD_PRIORITY_NORMAL,
      UINT nStackSize = 0,
      DWORD dwCreateFlags = 0,
      LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );

            <인자설명>

              . pfnThreadProc : 실행시킬 함수의 포인터

                 * 함수 형식

                   UINT 함수이름 (void* pParam)

              . LPVOID pParam : 함수에 전달할 인자 포인터

              . int nPriority = THREAD_PRIORITY_NORMAL : 쓰레드 우선 순위

              

우선순위 상수

내  용

THREAD_PRIORITY_TIME_CRITICAL

 우선순위 15

THREAD_PRIORITY_HIGHEST

 프로세스 우선 순위 + 2단계

THREAD_PRIORITY_ABOVE_NORMAL

 프로세스 우선 순위 + 1단계

THREAD_PRIORITY_NORMAL

 프로세스 우선 순위와 동일

THREAD_PRIORITY_BELOW_NORMAL

 프로세스 우선 순위 - 1단계

THREAD_PRIORITY_LOWEST

 프로세스 우선 순위 - 2단계

THREAD_PRIORITY_IDLE

 우선순위 1

 

              . UINT nStackSize = 0          : 스택 크기

              . DWORD dwCreateFlags = 0  : 실행 상태

              . LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL : 보안 속성


         - 쓰레드 종료

            . 쓰레드 자체내에서 종료(쓰레드 함수 끝부분에 넣는다.)

              void AfxThreadEnd(UINT 종료코드)

            . 외부 쓰레드에서 종료

              TerminateThread API함수 사용

 

 


       나) UI쓰레드

         : 함수 호출대신에 메시지를 받아 수행 하는 쓰레드 방식. CWinThread는 자체적으로

         메시지 큐를 갖게 된다. (Run 함수에 메시지 펌프 루틴이 들어가 있음). 따라서 어떤

         조건에 따라 다른 처리를 하는 쓰레드를 구성하고 싶은 경우 작업 쓰레드 보다는

         UI쓰레드가 편리하다.


         - 내부 실행순서

            ① 실행할 쓰레드 함수가 없으므로 곧바로 CWinThread의 InitInstance()함수를 실행

            ② Run() 호출. 메시지 펌프가 들어가 있음


         - 쓰레드 생성

CWinThread* AfxBeginThread(
      CRuntimeClass* pThreadClass,         // UI 쓰레드 클래스 객체 지정
      int nPriority = THREAD_PRIORITY_NORMAL,     // 우선 순위
      UINT nStackSize = 0,                          // 스택 크기
      DWORD dwCreateFlags = 0,                   // 시작 형태
      LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );     // 보안

         ex) AfxBeginThread(RUNTIME_CLASS(MyThread), 0, 0, CREATE_SUSPEND,0);


         <주의> 생성후 바로 동작하지 않도록 네 번째 인자에 CREATE_SUSPEND를 지정한다.

            시작은 ResumeThread() 함수로 한다.


            * UI 쓰레드 생성시 내부 동작

              ① CWinThread객체 생성

              ② 쓰레드 루틴 실행 (CreateObject)

              ③ 우선순위 결정(SetThreadPriority)

              ④ 쓰레드 시작(ResumeThread)


            cf. MFC _tWinMain에서 CWinThread역할

              => 생성된 메인쓰레드에 메시지 루프를 생성


       다) 작업쓰레드 vs UI쓰레드

         : 결국은 같은 클래스(CWinThead)에서 처리를 하는 데 유일하게 구분하는 기준은

         쓰레드 함수의 존재 여부(포인터의 존재)로 판단한다.

         .  CreateThread함수는 _beginethreadex를 호출하고 그 함수는 _AfxThreadEntry

            전역함수를 쓰레드 함수로 해서 실행 시킨다.

         . _AfxThreadEntry 함수 내용을 보면 m_pfn_ThreadProc멤버를 체크 하는 부분이 있는

            데 이것이 쓰레드 함수의 지정 여부를 판단하는 부분이다.

         . 만약 이 부분에서 m_pfnThreadProc멤버가 Null이 아니면 쓰레드 함수를 호출하고

            Null이면 CWinThread의 InitInstance() 실행후 곧바로 Run()함수를 호출해서 메시지

            루프를 돈다. 그리고 종료시에 ExitInstance() 호출.


       라) CWinThread클래스를 그대로 이용하는 방법

         ① CWinThread클래스를 상속 받는 새로운 클래스를 정의한다.

            ex) class MyThread : public CWinThread

         ② Run() 함수를 오버라이딩한다.

            => 쓰레드로 처리할 로직을 이곳에 정의 메시지 루프 대신에 처리 로직을 넣는다.

            ex) virtual int Run(void);

         ③ 외부 쓰레드(외부 함수)에서 쓰레드 클래스의 객체를 생성한다.

            ex) MyThread *my = new MyThread();

                my->m_bAutoDelete = TRUE;

         ④ 객체에 CreateThread함수를 호출 시켜서 쓰레드 로직을 실행 시킨다.

            =>  _AfxThreadEntry전역함수에 의해 Run함수가 실행된다.

            ex) my->CreateThread();         // Run()함수가 간접 호출됨.


       마) CWinThread 멤버들

         - 멤버 변수

            m_bAutoDelete : 쓰레드가 끝나면 m_hThread를 자동 해제 여부 지정.

              <주의>

                 Wait 계열 함수에 쓰레드 핸들을 지정하는 경우에는 FALSE로 해 두어야 한다.

                 TRUE로 해 둔 경우에는 쓰레드 클래스 객체를 delete하지 않는다.

            m_hThread    : 쓰레드 핸들

            m_nThreadID  : 쓰레드 ID

            m_pMainWnd  : Holds a pointer to the main window of the application.


         - 멤버 함수

            CreateThread : 쓰레드 시작 

            GetMainWnd : Retrieves a pointer to the main window for the thread.

            GetThreadPriority : Gets the priority of the current thread.

            SetThreadPriority : Sets the priority of the current thread.

            ResumeThread : Decrements the suspend count in a thread.

            SuspendThread : Increments the suspend count in a thread.

             ExitInstance : Override to clean up when your thread terminates.

            InitInstance  : Override to perform thread instance initialization.

            OnIdle : Override to perform thread-specific idle-time processing.

            PreTranslateMessage Filters messages before they are dispatched to the

               Windows CE TranslateMessage and DispatchMessage functions.

            Run :  Controlling method for threads with a message pump.

                 Override to customize the default message loop.



 


    

 

    바) Worker Thread 코딩 스타일

       ① 활용 1 => AfxBeginThread() 함수 사용

         - Thread 함수 정의

                UINT Sum(LPVOID pParam)

                {               ............        }


         - Thread 실행

CWinThread* pThread = AfxBeginThread(Sum, &si);
while(GetExitCodeThread(pThread->m_hThread, &dwExitCode))
{
if (dwExitCode != STILL_ACTIVE)
{
break;
}
else
{
WriteString(TEXT("."));
Sleep(20);
}
}


       ② 활용 2 => CWinThread 파생

         - CWinThread 파생 클래스 정의

class CSumThread : public CWinThread
{
DECLARE_DYNCREATE(CSumThread)

//protected: new를 사용하여 객체 생성이 가능하도록 public으로 수정한다.
public:
CSumThread();      // 동적 만들기에 사용되는 protected 생성자입니다.
virtual ~CSumThread();

public:
virtual BOOL InitInstance();
virtual int ExitInstance();
virtual int Run(void);
};


       - Run 함수 정의

int CSumThread::Run(void)
{
:
return 0;
}


       - 쓰레드 생성 실행

DWORD dwExitCode;

// CSumThread의 생성자와 소멸자를 public으로 변경해야 한다.
CSumThread* pSumThread = new CSumThread;

// 클래스의 멤버 변수 설정
pSumThread->m_bAutoDelete = TRUE;

// 쓰레드 루틴 실행
pSumThread->CreateThread(0 ,0);

// 쓰레드가 종료할 때까지 0.02초 간격으로 "."을 화면에 찍는다.
while(GetExitCodeThread(pSumThread->m_hThread, &dwExitCode))
{
if (dwExitCode != STILL_ACTIVE)
{
break;
}
else
{
WriteString(TEXT("."));
Sleep(20);
}
}

// 자동 소멸되도록 CSumThread::m_bAutoDelete를 FALSE로 하였기 때문에
// 소멸하면 아니 된다.
// delete pSumThread;

return 0;


http://blog.naver.com/renon79/120031476230