상세 컨텐츠

본문 제목

MFC Thread, 스레드로 드러우 그리기 OpenGL, 이미지 그리기 관련

프로그래밍 관련/MFC

by AlrepondTech 2022. 3. 10. 18:17

본문

반응형

 

 

 

 

 

=======================

=======================

=======================

 

 

 

출처: http://codens.info/287

 

Thread 비교, 사용법

강제 종료는 강력히 권장하지 않음

 

//====================

* AfxBeginThread <- 권장
    - 동작 

   - 내부적으로 CWinThread가 모든일을 함, CWinThread 리턴

        - 스레드 생성을 위해 CWinThread::CreateThread 함수를 호출
            - CWinThread::CreateThread 함수는 내부적으로 _beginthreadex 함수를 호출
        
    - 강제종료 - 함수안 : AfxEndThread(),  함수 밖 : TerminateThread();
    - CWinThread *pWinThread = AfxBeginThread( ThreadFunc1, &nMode);
    hThread = pWinThread->m_hThread;





//===========================

* CreateThread <- 비권장
    - 강제종료 : 함수안 : ExitThread(),  함수 밖 : TerminateThread();
    - hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFunc1, 
    &nMode, 0, &dwThreadID);




* _begingthreadex
    - 구 _begingthread 함수의 문제 : 
        쓰레드를 생성한후 바로 ::CloseHandle 함수를 호출하여 생성된 스레드의 핸들을 제거 
        -> _beginthread 함수 호출 이후에 이 스레드 핸들에 접근 불가
    - 쓰레드 함수에서 ::CloseHandle 함수를 호출해 주어야 함

    - 강제종료 : 함수안 : _endthreadex(),  함수 밖 : TerminateThread();
    - hThread = (HANDLE)_beginthreadex(NULL, 0, 
    (unsigned ( __stdcall *)( void * ))ThreadFunc1, &nMode, 0, (unsigned *)&dwThreadID);


* CWinThread
    - UI 쓰레드(메시지 처리가능한 쓰레드)를 만들수 있음
    - 사용법 
        - CWinThread를 상속 받는 자식클래스를 만든다.
        - 생성자,소멸자를 public으로 만든다.
        - virtual int Run();추가

    - pThread = (apTestThread *)AfxBeginThread(RUNTIME_CLASS(apTestThread ) );





//===================================================================================

int g_bStopThread=0;
//
static DWORD  ThreadFunc1(LPVOID lpParam)
{
    int *pnMode = (int*)lpParam;

    while( !g_bStopThread ){
        TRACE(_T("ThreadFunc1-실행\n"));Sleep(1000);
    }

    switch(*pnMode)
    {
    case 1:    ExitThread(1); break;//CreateThread
    case 2:    _endthreadex(1); break;//_beginthreadex
    case 3:    AfxEndThread(1); break;//AfxEndThread
    }
    return 1;
}

DWORD  CTestDlgMfc6Dlg::ThreadTest()
{
    DWORD dwThreadID=0;
    HANDLE hThread = 0;
    int nMode=0;
    g_bStopThread = 0;
    //==============================================================================
    //1. API - CreateThread 사용 - Win API만 사용 가능, MFC, C 함수는 사용 불가
    //TRACE(_T("ThreadTest-API-시작\n"));
    //CreateThread 사용 - Win API만 사용 가능, MFC, C 함수는 사용 불가    
    nMode=1;
    //hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFunc1, &nMode, 0, &dwThreadID);
    //ExitThread

    //==============================================================================
    //2. API - _beginthreadex 사용 - Win API, C함수 사용 가능    
    nMode=2;
    //hThread = (HANDLE)_beginthreadex(NULL, 0, 
    //(unsigned ( __stdcall *)( void * ))ThreadFunc1, &nMode, 0, (unsigned *)&dwThreadID);
    //_endthread


    //==============================================================================
    //3. MFC - AfxBeginThread
    //TRACE(_T("ThreadTest-MFC-시작\n"));
    nMode=3;
    CWinThread *pWinThread = AfxBeginThread( (AFX_THREADPROC )ThreadFunc1, &nMode);
    hThread = pWinThread->m_hThread;


    //==============================================================================
    //4. MFC - CWinThread
    TRACE(_T("ThreadTest-MFC, CWinThread-시작\n"));    
    apTestThread* pThread = 0;
    
    //시작 방법 1
    //pThread = new apTestThread() ;    pThread->CreateThread(CREATE_SUSPENDED) ;
    
    //시작 방법 2
    pThread = (apTestThread *)AfxBeginThread(RUNTIME_CLASS(apTestThread ) );
    //pThread = (apTestThread *)AfxBeginThread(RUNTIME_CLASS(apTestThread ), 
    //THREAD_PRIORITY_NORMAL, CREATE_SUSPENDED, 0 );

    //설정
    pThread->m_bAutoDelete = false ; // 기본은 true
    //pThread->sName = _T("Test") ; // 
    pThread->ResumeThread() ;
    hThread = pThread->m_hThread;

    //===============================================================================
    // 쓰레드 종료
    TRACE(_T("쓰레드-종료 기다림...\n"));    
    DWORD dwRetCode = ::WaitForSingleObject(hThread, 2000);//INFINITE
    if (dwRetCode == WAIT_OBJECT_0){// 정상 종료
        TRACE(_T("쓰레드-정상 종료\n"));        
        //ReleaseMutex(hMutex);
        CloseHandle(hThread);//
        //delete(pThread) ;//m_bAutoDelete = false인 경우
    
    }else if(dwRetCode == WAIT_TIMEOUT)    {
        // 제한시간동안 쓰레드라 종료되지 않음
        TRACE(_T("쓰레드-종료 되지 않음 - 시간 지남\n"));
        //g_bStopThread = 1;//리소스 해제 할수 있음, ExitThread, _endthread, AfxEndThread
        TerminateThread( hThread , 0);//리소스가 해제 안될수 있음
        
    }else if (dwRetCode == WAIT_ABANDONED){// Mutex를 선점하던 Thread가 강제로 종료됨
        TRACE(_T("쓰레드-Mutex를 선점하던 Thread가 강제로 종료됨\n"));
        //ReleaseMutex(hMutex);
        
    }else if (dwRetCode == WAIT_FAILED ){// WaitForSingleObject 함수 에러
        TRACE(_T("쓰레드-WaitForSingleObject 함수 에러\n"));
        
    }
    
    //==============================================================================
    

    //
    TRACE(_T("ThreadTest-종료\n"));
    return 1;
}

 

 

 

 

=======================

=======================

=======================

 

 

 

출처: http://fingerdev.tistory.com/25

# Thread (쓰레드) 의 진실

프로그래밍을 하다보면 쓰레드를 많이 사용하게 된다. 하지만 쓰레드는 굉장히 위험한 엔진?이므로 

잘 돌려야 한다. 

 

     대부분 쓰레드를 생성할때 사용할 수 있는 함수들이 

     CreateThread 함수, _beginthread 함수, _beginthreadex 함수, AfxBeginThread 함수  

     이렇게 4가지 정도 사용되어지는데..

 

     * CreateThread 함수 

        : 쓰레드 생성 함수  (사용자제요망)

          C/C++ 표준함수를 호출하려하면 문제가 발생할 수 있음. 

 

     * _beginthread 함수 

        : C/C++ 표준함수가 안전하게 실행되어질 수 있다.

          하지만 생성시 반환되는 핸들을 무효화시켜 커널 오브젝트에 접근할 수 있는 방법을 

          막아버리는 문제점이 있다. 

 

     * _beginthreadex 함수 

        : C/C++ 표준함수가 안전하게 실행되어질 수 있다.  (사용권장) 

          마지막엔 CloseHandle 로 핸들을 닫아 주어야 한다.

           _beginthread 함수를 개선시킨 함수이다. 

             

     * AfxBeginThread 함수 

        : MFC 에서 제공되어지는 클래스/함수 등을 사용할 때에는 특히 UI 관련된 작업을 할때에는 

           반드시 AfxBeginThread 함수로 쓰레드를 생성하여야 함.  (사용권장) 

           내부적으로는 _beginthreadex 함수가 사용되어진다.

 

 

//--------------------------------------------

// _beginthreadex 예제

// _beginthreadex 예제

 #include <process.h>

   {

      HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, threadFunc, NULL, CREATE_SUSPENDED, NULL);

       if (!hThread)

      {

	   TRACE(" Error Thread \r\n");	

	   return;

      }

      ResumeThread(hThread);

      SuspendThread(hThread);

      ResumeThread(hThread);

      WaitForSingleObject(hThread, INFINITE);

      CloseHandle(hThread);

   }



   unsigned __stdcall threadFunc( void* pArguments )

   {

      ...	

      return 0;

   }

 

----------------------------------------------------------------------------------------

// AfxBeginThread 예제



   #include <afxwin.h>



   CWinThread *pThread = AfxBeginThread(funcThread, NULL);	

      

   static UINT funcThread(LPVOID pParam);

   UINT CT_ThreadAfxDlg::funcThread(LPVOID pParam)

   {

	TRACE(" THREAD \r\n");

	return 0;

   }

출처: http://fingerdev.tistory.com/25 [FingerDev (핑거데브)]

 

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://egloos.zum.com/torpedo7/v/79879

 

MFC에서 스레드를 생성하는 함수는 두가지가 있다. 잘 알다시피
AfxBeginThread() 와 CWinThread::CreateThread() 이다.
이 둘은 어떻게 다른것일까?
그리고 ::CreateThread() 나 _beginthreadex() 는 쓸수 없는것인가?

원래 스레드를 생성하는 함수는 WIN32 api 인  ::CreateThread()와 _beginthread,ex
() 밖에 없다.
그런데 MFC 클래스로 스레드 사용을 객체화 시킨것이 CWinThread 이다.
그리고 CWinThread 는 스레드를 생성하기 위해 _beginthreadex()를 호출한다.

AfxBeginThread()는 워커스레드 혹은 UI 스레드를 보다 쓰기쉽게 만든 범용함수라 할
수있다.
AfxBeginThread()의 두가지 버전중에서 스레드 함수 포인터를 넘겨주는 버전은
CWinThread 와 별로 상관이 없을것이라는 착각을 하기 쉬운데 그렇지 않다.
AfxBeginThread() 는 CWinThread 를 내부적으로 사용하고 있다. 물론 두가지 버전
모두에서다. 이같은 사실은 AfxBeginThread() 가 리턴하는 것이 CWinThread 포인터
라는데서 쉽게 짐작할수 있다. 또 스레드를 구현한 MFC 소스코드를 살펴봄으로써 
보다 자세히 알수 있다.

MFC 소스코드중 thrdcore.cpp 이란 파일에서 우리는 CWinThread 의 소스코드를 볼수
있다.
먼저 AfxBeginThread() 를 찾아보자.

첫번째로 
CWinThread* AFXAPI AfxBeginThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam,
    int nPriority, UINT nStackSize, DWORD dwCreateFlags,
    LPSECURITY_ATTRIBUTES lpSecurityAttrs) 
{
....
         CWinThread* pThread = DEBUG_NEW CWinThread(pfnThreadProc, pParam);

정확히 166라인에 AfxBeginThread() 가 CWinThread 객체를 생성하는 것을 볼수있다. 
그리고 조금아래에서 

        if (!pThread->CreateThread(dwCreateFlags|CREATE_SUSPENDED, nStackSize,
        lpSecurityAttrs))
        {
              pThread->Delete();
              return NULL;
    }

와 같이 생성한 CWinThread 객체의 메소드인 CreateThread() 를 호출해서 스레드를
생성는데 CREATE_SUSPENDED 로 실행이 중지된 스레드를 생성한다.
그리고 아래에서 AfxBeginThread() 의 파라미터로 전달한 우선순위로 변경한뒤
역시 파라미터로 전달한 dwCreateFlags 에 맞게 스레드를 Resume 시킬것인지를 결정
한다.
이와 같은 일련의 과정은 우리가 손수 CWinThread 객체를 생성해서 사용하는 일반적

방법과 정확히 일치하며 CWinThread 객체를 생성하는 가장 정석의 방법이다.

바로 아래에 나오는 두번째 AfxBeginThread() 역시 첫번째와 별 틀린점이 없다는 것

또한 볼수 있다.

그러면 CWinThread 를 사용자가 그냥 쓰면 되지 왜 AfxBeginThread() 가 있는가?
그에 대한 해답은 MFC 클래스를 만든 MS의 개발자들이 해야 겠지만 적어도 내가 보기

특별한 이유는 없다. 
단지 CWinThread 객체를 생성하고 스레드를 시작시키는 일련의 작업을 보다 쉽게 처

하기 위해 있을 뿐이다. 편의를 위해 생긴 함수랄까? AfxMessageBox() 와 
::MessageBox()처럼.

그렇지만 AfxBeginThread() 를 쓰는것은 일반적으로 지극히 권장할만 하다.
그것은 AfxBeginThread()는 프로그래머가 수고해야할 여러가지 작업들을 대신해 주고
있기 때문이다. 그것은 ASSERT_VALID()와 같이 보다 코드의 위험성을 줄일수 있도록
하는것들과 발생할지 모르는 메모리 예외오류 등을 매끄럽게 처리하고 있다.
만약 여러분이 CWinThread 를 그냥 쓴다고 하면 AfxBeginThread() 가 처리해 주는 이

일들에 소홀할지 모르고, 그것은 단순히 편리함 이상의 안전함을 포기하는 것과 같
다.
시작이 있으면 끝도 있는법. AfxBeginThread()와 쌍을 이루는 AfxEndThread() 도 있
다.
그러나 쓸일은 거의 없다고 본다.

*참고*
코드에 _MT 라는 매크로가 자주 등장하는 것을 보는데 이는 컴파일러 옵션에서 멀티
스레드
를 지원하도록 할때 정의되는 매크로다. 디폴트로 _MT 는 정의가 되므로 걱정하지 않
아도
되고 이는 Project->Setting->C/C++ 탭의 Code Generation 카테고리에서 볼수있다.


이제 CWinThread의 소스코드를 보도록 하자. 조금 아래로 내려가면 CWinThread의
소스코드를 볼수있다. 앞서 설명했지만 CWinThread 는 _beginthreadex()를 호출해서
스레드를 실행한다.

CWinThread의 생성자에서 볼수있는 흥미로운 점은 m_pMainWnd 라는 멤버를 가지고
있다는 것이다. 위자드를 통해 어플리케이션을 만들때 CWinApp 에서 상속을 받는것으

시작한 것을 기억하는가? 그 CWinApp 는 CWinThread에서 상속받았다. 
따라서 메세지펌핑이 CWinThread 클래스에 존재하는것은 당연한 것이다.
특히 UI 스레드를 사용하는데 있어 CWinThread 가 메세지 루프를 돌린다는 점은 매
우 당연
한 사실이다. UI스레드는 메세지 루프를 가지는 스레드로써 워커스레드와 구별된다.
간단히 이야기하자면(실제로는 복잡한 이야기지만) UI 스레드는 워커스레드에 메세지
펌핑
이 첨가된 스레드의 형태라고 할수있다. 싫든좋든 하나의 프로세스로 이루어진 어플
리케이션
이 최소한 하나의 스레드를 가진다고 말할수 있는것은 곧 그것이 UI스레드 이기 때문
이다.
만약 MFC가 UI 스레드를 만들지 않았다면 여러분은 UI 스레드 같은 클래스를 구현하
기 위해
워커스레드에서 메세지 펌핑 및 윈도우프로시저를 구현하기 위한 복잡한 작업들을 손

해야 할것이다. 그런데 UI스레드가 이미 CWinThread로 구현되어 있으니 그 얼마나 다
행스러운가.

자. 다시 본래 이야기로 돌아가자.

CWinThread 에서 스레드를 생성시키는 CWinThread::CreateThread() 를 찾아라.
AfxBeginThread()에서 잠시 보았던 CWinThread::CreateThread()의 소스코드가 주루
룩 나오는가?
그중에서 스레드를 생성하는 루틴이 있는 408라인 _beginthreadex().
앞서 이야기 한것처럼 CWinThread() CWinThread::CreateThread()에서 _beginthreadex
()를
호출하여 새로운 스레드를 생성한다. 그리고 생성된 스레드는 CWinThread의 멤버인 
m_hThread 에 집어넣는 것을 직접 볼수있다. 그리고 그게 모두이다. That"s all.
Event 객체들이 왜 나오지? 하고 궁금증을 갖는 사람이 있을 것이다. 소스코드를 더 
읽어
보면 알겠지만 _beginthread()에 넘겨주는 스레드 프로시저 함수의 포인터가 무엇인

잘보면 안다. 그것은 우리가 지정한 프로시저 포인터가 아니고 _AfxThreadEntry() 라
는 
함수의 포인터다. _AfxThreadEntry() 가 뭐일까? 
갑자기 이놈이 나온 이유는?
CWinThread 가 워커로 생성되거나 혹은 UI로 생성된다면 분명 하는 작업이 틀리겠지.
워커스레드가 InitInstance()가 실행될리 만무하지 않는가. 물론 Run() 이나 
ExitInstance()도.
_AfxThreadEntry()는 CWinThread 가 생성하는 스레드를 안전하게 생성되게 하고 메세

루프를 돌리기 위한 준비를 갖추고 또 스레드가 가져야할 여러가지 속성 이나 메인 
윈도우
와의 연결을 처리한다.
_AfxThreadEntry() 는 44라인에 소스코드가 있다. 이체로운 점은 CWnd 객체를 하나 
사용
한다는 점과 이벤트객체가 사용된다는 점이다. 
그렇다.
약간 이상하다고 생각할지 모르지만 CWinThread 에서는 워커든 UI든 CWnd 객체가 사
용된다.
그것은 m_pMainWnd, 즉 메인 윈도우가 없다면 이 스레드가 메인윈도우가 되기 위해서
다.
CWinApp 가 CWinThread에서 상속된다는 사실을 항상 명심하라.
이벤트 객체는 스레드를 생성한 CreateThread() 가 실제로 스레드가 온전하게 실행되
었다는
것을 확인하기 위한 동기를 맞추기 위해 필요하다.
_AfxThreadEntry() 는 이미 스레드 프로시저다. 이미 실행되고 있는 스레드 함수다.
그러나 아직 우리가 지정한 스레드 함수는 실행한적도 없다. 그 이전에 
_AfxThreadEntry()
가 각종 선행작업을 모두 마무리 할때 까지 CreateThread() 는 결과를 지켜볼수 있도

하는 것이다. 이런 선행작업이 비로소 마무리 되었을때 CreateThread() 는 성공적으
로 
스레드가 시작되었다고 판단하고 반대의 경우는 실패 했다고 본다.
110 라인을 보면 이제서야 우리가 지정한 스레드 프로시저가 실행됨을 볼수있다.
그것이 워커라면 그냥 함수 하나 달랑 실행하는 것이 될것이고 UI 스레드라면
InitInstance() 를 실행하는 것을 볼수 있다. 만약 InitInstance()가 가상함수로
자식 클래스에서 구현하지 않는다면 CWinThread::InitInstance()는 무엇을 리턴하는
가?
FALSE 다. 그러면 무조건 스레드가 무조건 종료할수 밖에 없다. 그래서 InitInstance
()
는 반드시 리턴해서 초기화 작업을 수행토록 하고 성공적일 경우 TRUE를 리턴시켜야 
한다.
그래야만 믿에서 드디어 Run() 이 실행되고 WM_QUIT 이 들어올때 까지 메시지 루프를
돌게 되는 것이다. 이게 CWinThread 객체를 만들면 무조건 위자드가 InitInstance()

오버라이드 시키는 이유이다.
둘다 실행을 마친뒤는 _AfxThreadEntry()의 끝에 치닫는다. 즉 _AfxThreadEntry()가 
리턴함으로써 실제로 스레드가 종료되게 되는 것이다.


이제 진짜 중요한 부분. CWinThread의 핵심. CWinThread::Run() 을 보자.
대게 CWinThread의 오버라이드 가능한 Run()에 대한 쓰임세에 혼동하는 사람들이 많
은데
그것은 아마도 함수의 이름때문에 그런것 같다.
UI 스레드와 워커스레드(이것은 마치 함수 하나를 비동기적으로 실행하는 것)의 차이

모른체 워커스레드만 쓰다가 UI스레드를 접할때 생기는 혼동으로 Run 이 워커스레드

프로시저 함수 본체 쯤으로 생각하는 오해이다. 뭐 코드로 보면 이렇게..

UINT MyThreadFunc( void *p )
{
      CWinThread::Run()
}

-_-;

아니다. 아니다. 이게 아니다.
CWinThread::Run() 은 워커스레드에서 처럼 무엇을 실행하기 위해 오버라이드 가능한
메소드가 아니라 메세지 루프를 돌리는 메소드다. UI스레드를 쓸때에도 특별한 경우

제외하고는 거의 오버라이드 할 일도 없다. 또 실제로 Run을 오버라이드 하고 디폴트
CWinThread::Run() 를 막고 거기서 while()을 돌려 워커스레드 처럼 쓰는 경우도 있
는데
그럴바에야 워커스레드를 쓰는게 낫다.
그럼 이놈이 도대체 무슨일을 하기에 이름도 거창한 Run()이 되었는지 살펴보자.

// main running routine until thread exits
Run()을 설명한 주석이 벌써 모든것을 말하는군.
스레드가 끝날때 까지 돌고도는 함수. 메세지 루프를 돌려야 하니깐 돌지.
흥미로운 부분은 

// phase1: check to see if we can do idle work
while (bIdle &&
     !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))
{
         // call OnIdle while in bIdle state
         if (!OnIdle(lIdleCount++))
              bIdle = FALSE; // assume "no idle" state
}

이 부분이다.


check to see if we can do idle work  !

idle working 을 할수 있는지 체크하고 가능하면 idle working 을 하라!
PeekMessage() 로 먼저 주어진 메세지큐에서 메세지를 살펴보고 메세지가
하나도 없으면 OnIdle() 를 호출하라.
즉 처리해야할 메세지가 없으면 노느니 할일 있으면 해라 라는 뜻이다.
재미있는점은 lIdleCount 가 증가하는 것과 OnIdle()가 FALSE 를 리턴하면
루프를 빠져나간다는 점이다.

OnIdle() 은 메세지가 없을때 반복적으로 호출될수 있는 오버라이드 가능한
메소드임을 알았다. 그런데 lIdleCount는 왜 증가 시켰을까?
그것은 528라인의 CWinThread::OnIdle(LONG lCount) 소스코드를 보면 이해할수 있다.
OnIdle() 의 설명은 책에 다 나오는 내용이니깐 뭐 더 설명할것 없겠고..

다시 소스코드로 돌아오자.
OnIdle() 작업이 끝나면 예상대로 메세지 펌핑을 한다. 메세지를 가져와서 WM_QUIT
가 아니면 윈도우프로시저를 호출해서 메세지 핸들러들을 수행하게 하고 메세지가
없을때 까지"while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))"
계속 펌핑을 한다. 그러다 메세지가 없으면 또 OnIdle() 처리를 하겠지.

그런데 UI스레드를 쓰는 목적은 OnIdle() 에서 끄적거릴려고 쓰는게 아니다.
UI스레드의 핵심은 메세지다. 메세지를 쓰기위한게 주 목적이고 OnIdle()은 그야말로
별책부록이다. 
우리는 PostThreadMessage() 를 통해 특정 UI 스레드에 메세지를 보낼수 있다.
실전에선 대부분 User Define 메세지를 많이 쓰게 될것이다. 이렇게 메세지를 처리하

구조의 UI 스레드는 책에서 설명하는것 처럼 화면에 나올 UI를 처리하는 목적으론
거의 쓸일없다. 오히려 메세지큐를 이용해 멀티스레드를 아주아주 쉽게 쓸수 있다.

가령 멀티스레드에서 여러개의 스레드가 특정한 JOB이 생기지 않을때 CPU자원을 쓰지
않고 휴지상태에 있어야 하고 특정 JOB이 발생할때만 처리되어야 할때 워커스레드에

대부분 WaitFor..Object()를 이용하려고 할것이다. 그러나 UI 스레드에서는 메세지가
없으면 자동으로 쉰다(진짜 편리하지 않는가?).
그리고 극도의 상황에서 여러개의 JOB이 발생할때 JOB이 큐잉되어야 한다면 그것을
메세지큐를 이용해 쉽게 큐잉할수 있다는 점이다.
따라서 처리할 JOB을 여러 멀티스레드에게 골고루 배분하는 점만 잘 구현하면 이런
형태의 구조를 아주 쉽게 UI 스레드로 구현할수 있을것이다.
만약 여러분이 워커스레드로 구현한다면 폭주하는 JOB의 큐잉까지 신경써야 할텐데
그것이 거의 퍼펙트하고 안전한 방법으로 처리할수 있는 것이다.
그러나 물론 메세지를 이용한다는 자체가 약간 무겁기 때문에 멀티스레드 끼리의
경합하에 JOB을 수행토록 한다면, 그것이 또 I/O의 문제라면 IOCP 를 써서 해결하면
메세지큐를 검사하지 않기 때문에 가볍고, 커널객체인 이유로 속도가 빠르다.


지금까지 설명하면서 CreateThread() 를 꼭 CWinThread::CreateThread() 라고 표현한
것을 눈치챘기를 바란다.
WIN32 API 인 ::CreateThread() 는 CWinThread::CreateThread()랑은 많이 다르다.
CWinThread::CreateThread() 는 ::CreateThread() 와는 달리 MFC에서 사용되기 위한
많은 부분이 첨가되고 자원에 대한 접근 및 스레드가 온전하게 생성되고 실행되도록
검사하며 특히 C런타임 함수들이 안전하게 엑세스 되도록 보장하는 일들을 수행한다.
벌써 CWinThread 의 긴 소스코드를 보면서 느꼈으리라 본다. ::CreateThread() 로
어찌 지금까지 설명한 내용들을 코딩하리.. 난 게을러서 못한다..



*스레드 종료

스레드 종료에 대한 최고의 방법은 간단하다.
스레드가 더이상 실행될 필요가 없을때 스스로 종료할수 있도록 하고 여러분은 
아무런 걱정을 하지 않으면 된다. 그게 모두다. 
또 스레드를 가진 부모 프로세스가 종료하면 스레드는 스스로 죽는다.
그리고 우리는 OS가 스레드를 종료시키고 스레드 스택과 사용되었던 리소스가
안전하게 재거되고 반환되었다고 믿는 수 밖에 없다.

그러나 스레드가 교착상태에 빠지거나 스스로 빠져나올 조건이 알수없는 이유로
걸릴수 없을때 강제로 스레드를 종료하는 방법이 존재한다.
그러나 어떠한 방법으로든, 스레드를 강제로 종료하는 것은 최후의 수단이다.

일단 종료시 주의할점들을 몇가지 살펴보자.

그럴리 없다고 보지만 여러분이 직접 _beginthreadex() 로 스레드를 생성했다면
_endthreadex()로 스레드를 종료하게 되는데 _endthread()와는 다르게 이 함수는
스레드 핸들을 자동적으로 닫아주지 않는다. 따라서 여러분은 _endthreadex()후
CloseHandle()을 호출할 필요가 있다. 그러나 나는 아직까지 _endthreadex()를
호출하도록 코드를 만들어 본적이 없다. 대게의 경우 _endthreadex()를 호출하지
않더라도 스레드가 스스로 리턴될때 스레드스택은 파괴되고 리소스는 반환된다. 믿어
라.
어쨋든 이 부분에 대한 MSDN의 원문을 간추려 보면 아래와 같다.

_endthread automatically closes the thread handle. 
(This behavior differs from the Win32 ExitThread API.)
Therefore, when you use _beginthread and _endthread, do not explicitly 
close the thread handle by calling the Win32CloseHandle API. 

Like the Win32 ExitThread API, _endthreadex does not close the thread handle. 
Therefore, when you use _beginthreadex and _endthreadex, you must close 
the thread handle by calling the Win32 CloseHandle API.

또 유의할 사항이 있다. 이것역시 여러분이 거의 주의할 일이 없겠지만..
만약 _beginthread() 나 _beginthreadex()로 스레드를 시작했다면 WIN32 API인
ExitThread()를 호출하지 않는게 몸에 좋다는 것이다.
ExitThread()는 _beginthread()와 짝이 아니다. 그건 ::CreateThread()와 짝이다.
MSDN에서는 _beginthread,ex() 를 쓸 경우 static library 로 링크할경우 사용되는
LIBCMT.LIB에서는 사용된 리소스가 제대로 해제되지 않을것이라고 경고하고 있다.
원문을 첨부한다.

Note
For an executable file linked with LIBCMT.LIB, do not call the Win32 
ExitThread API;
this prevents the run-time system from reclaiming allocated resources. 
_endthread and _endthreadex reclaim allocated thread resources and then call 
ExitThread.


::CreateThread()로 스레드를 만들었을때도 마찬가지다.
ExitThread()는 안써도 된다. 즉 스레드가 그냥 종료될수 있도록 하면 그만이다.

MFC의 경우 AfxBeginThread() 나 CWinThread::CreateThread() 를 이용해서 스레드를
만들었을 때 여러분은 정말 다행스러운 선택을 한것이라고 말하고 싶다.
적어도 위의 방법은 최대한 여러분에게 안전하게 스레드가 종료될수 있도록 하는
작업들을 내부적으로 대신하고 있다. 여러분은 단지 m_bAutoDelete = TRUE를 
해주는 것만으로 스레드가 리턴될때 자동으로 객체까지 해제되게 할수 있다.
그러나 여기엔 약간의 위험이 있다. 만약 m_bAutoDelete 가 TRUE 라면 스레드가
종료한 후에 객체가 파괴 되므로 그후에 객체를 엑세스 해서는 안된다는 것이다.
엑세스가 안전한지, 그리고 스레드가 유효한지를 판단하기 위해 객체를 엑세스 할때
마다 GetExitCodeThread() 가 STILL_ACTIVE 인지 검사하는 것은 아주 멍청한 방법
이란걸 이미 여러분을 짐작 했을것이다.
따라서 이런 위험을 피하기 위해서는 CWinThread 객체의 스레드가 종료함을 보고받던

혹은 생성시에 개체포인터를 ASSERT_VALID() 를 이용해 온전한지 검사해 보는 방법이
다.
어쨋든 이런 일이 일어나는것은 버그이기 때문에 이렇게 되지 않도록 스스로 안전한
방법을 강구해야 한다. m_bAutoDelete 를 FALSE로 하고 객체를 직접 delete 하는것도
방법이 될것이다.

만약 UI 스레드를 쓴다면 더 간단하다. 여러분은 스레드의 메시큐에 WM_QUIT 메세지
가 
담기도록 하면 그만이다. PostQuitMessage()  그것으로 그 일을 할수 있다는 것은
이미 잘 알것이다. 그러나 언제까지나 Post 라는 점을 명심하라. Post 는 메세지를
부치고 바로 리턴되지 그 메세지가 처리될때까지 기다리지 않으므로 지금 
PostQuitMessage()
를 했다고 해서 바로 다음 스레드가 종료했다고 생각하면 오산이다. 그렇지 않는가? 
큐에 메세지를 담았을뿐 아직 스레드는 PostQuitMessage() 다음줄을 실행하고 있을 
뿐이지
메세지를 디스패치 한적 없다. 만약 스레드 밖에서 UI 스레드를 종료시키고자 한다면
약간 복잡하다. 적어도 지금까지 아는 방법으로는 WM_QUIT을 유도하는 방법 뿐이다.
그것은 PostThreadMessage() 로 메세지를 보내서 그 메세지 핸들러가 
PostQuitMessage()
를 호출하는 것이다. 그리고 PumpMessage() 가 GetMessage()로 WM_QUIT을 읽어 FALSE

리턴하도록 하고 ExitInstance() 를 수행함과 동시에 Run()을 리턴시키 도록 하는 것
이다.
이는 WM_DESTROY를 처리하는 윈도우프로시저의 수행방법과 거의 일치한다고 볼수있
다.
이 방법은 적어도 Release 모드에서는 잘 동작한다. PumpMessage()의 소스코드를 찾
아보면
알겠지만 디버그 모드에선 검사가 좀 많다. 나는 경험적으로 이러한 시도가 100% 안
전하다고
말할수 없다는 것을 알린다. 단지 경험적인 방법일뿐 정설인지 아닌지는 나도 모른
다.
그것은 디버그 모드에서 WM_QUIT 를 받았음에도 불구하고 PumpMessage() 에서 의도하
지 않은
작업을 시도하여 ASSERT()에 걸리는 것을 수차례 목격했기 때문이다.
이에대한 정확한 해답을 아직 밝히지 못했다. 어쨋든 이러한 종료작업도 마찬가지로
PostThreadMessage() 한 놈과 PostQuitMessage()할 놈이 서로 스레드이고 이들둘은 
동시에
실행되고 있다고 할수 있지만, PostThreadMessage() 를 호출한후 바로 종료를 기대하

스레드가 WM_QUIT을 처리할수 있다는 것은 절대 있을수 없는 상상이다. 기대도 하지
마라.
그런일은 절대 없다. 그래서 PostThreadMessage()로 스레드 종료를 기대 했다면 스레
드가
온전히 종료했는지를 기다려야 하며 그것은 누차 설명하지만 WaitFor..Object() 와 
GetExitCodeThread() 로 확인할수 있다. 그 이후가 되어서야 비로소 스레드가 종료했
다고
인정할수 있는 것이다. 언제나 그렇지만 스레드가 휴지상태로 들어가야 하는 순간이
라면
Sleep(0) 로 같은 우선순위의 스레드에게 CPU 사용권을 양보할 필요가 있고 이는 아
주 
유용하다.


  
자 이제 그렇게 부드러운 종료가 일어나지 않는다고 가정할때..
부득이 스레드를 강제로 종료해야할때 그 시나리오는 다음과 같다.

일단 여러분은 스레드핸들을 WaitFor..Object()에 넣어서 스레드가 종료하도록
기다리게 할수 있다. 그러나 무한정 기다릴수 없기에 타임아웃을 걸것이다.
그리고 타임아웃이 지난후 GetExitCodeThread() 로 스레드가 여전히 STILL_ACTIVE인

체크할수 있다. 만약 STILL_ACTIVE 라면 스레드는 아직도 종료할 생각을 안하고
혼자서 열심히 돌고있다. 이제 이놈을 죽여야 겠다.
극약처방인 TerminateThread()로.
그러나 단지 죽이는 것일뿐 사실 여러분이 얻을수 있는것은 아무것도 없다.
왜?
그것은 스레드를 강제로 종료하면 치뤄야 되는 댓가가 있기 때문이다.
일단 강제종료는 스택을 안전하고 깨끗하게 파괴하지 못한다. 지금 상태 에서 그냥
실행만 종료될 뿐이고 스택과 사용된 리소스는 해제된다는 보장이 없다.
뭐 달리 해제할 방법도 없다(아직 나는 모른다). 그냥 프로세스를 종료하고 OS가
알아서 잘 재활용하기를 기대할 뿐이다. 
또 그것 뿐이냐. 스레드가 사용한 크리티컬 섹션은 파괴되지 않게 되므로 그 섹션에

뭔가를 기다리는 모든 다른 스레드들은 교착상태가 될것이다. 끔찍하다!
나름데로 해결책이라면 그 크리티컬 섹션과 연관된 모든 스레드도 같이 종료시켜야 
할것이고, 최악의 순간엔 프로세스를 종료해야 할지도 모른다.
또 있다. MSDN 왈 TerminateThread() 는 커널을 흔들어 버린다. 지고지순 해야할 커
널이
불안해 진다니... 말할것도 없다. 시스템은 언제 어디서 어떻게 숨을 거둘지 모른다.
이렇게 강제종료는 우리에게 도움 주는게 없다. 해만 입힌다. 
만약 꼭 죽어도 강제종료를 해야 겠다면 그냥 프로세스를 종료하게 하는 것을 권한
다.
적어도 우리 손으로 종료시키는것 보다 OS가 온전하게 종료할수 있게끔 기회를 주라

것이다. 실제로 프로그램은 종료하지만 이 방법은 시스템을 보다 안정적으로 유지할

있게 할지도 모른다. 특히 여러분이 서버를 만든다면... 컴퓨터 뻗게 하느니 얌전하

프로그램만 종료되는게 얼마나 신사적인가.



*멀티 스레드

이제 멀티스레드로 넘어오자. 흔히 멀티 스레드를 쓸때 우리는 두가지 모델을 많이 
구현
한다. 서로다른 작업들을 하는 여러개의 스레드를 동시에 돌리거나, 같은 작업을
하는 여러개의 스레드를 동시에 돌리는 일. 둘다 멀티스레드다.
전자쪽은 주로 알고리즘상으로 효율을 극대화할때 많이 쓰이고 후자쪽은 특정 작업을
병렬적으로 수행해야 할때 많이 쓰인다. 
둘다 멀티스레드를 받쳐줄수 있는 하드웨어의 지원이 필요하다. 즉 JOB을 처리할 CPU

개수가 많아야 한다는 점이다. 물론 WIN32 OS 는 선점형 멀티스레딩을 통해 우선순위
데로
스레드를 스위칭 하기는 하지만 어디까지나 논리적으로 그렇다는 거고 물리적인 뒷받
침이
없다면 멀티스레드는 퍼포먼스를 충분히 발휘할수 없다. (못쓸 상황이란 뜻은 아니
다!)
예를들어 CPU하나에서 1에서 10000까지 합을 구하는데 스레드 2개로 나눠서 하면 1개
로 
할때보다 속도가 빨라질까? 

택도없는 소리.. 
더 느리다. 오히려 스레드간의 스케줄링 때문에 더 느리다. CPU만 더 괴로울 뿐이다.
그러나 멀티스레드를 이용해서 보다 나은 알고리즘적 성능향상을 꾀할수 있는 상황이
라면
분명 도움이 된다. 또 CPU 를 늘리면 멀티스레딩은 그제서야 제빛을 낼수 있을 것이
다.

멀티스레드가 되면 일단 골치가 아파지기 시작하는것이 자원에 대한 동기화다.
즉 전역변수 a가 있을때 여러개의 스레드가 동시에 a 값을 바꾸려고 하면 엉망진창이
되겠지. 예를 들자면..

int g_a;

Thread()

   g_a = GetA();
   g_a++;
   printf( "%d", g_a);
}

Thread() 를 여러개 돌리면 모든 경우에 있어서 g_a 가 얼마가 될지 알수 없다.
5개의 스레드가 돌면서 그중 3번 스레드가 GetA()로 받은 값이 9일때 3번 스레드는
10을 출력하기를 기대하겠지만 그 값은 10이될지,11이 될지,12가 될지.. 알수 없다는
것이다.
그래서 멀티스레드는 동기화가 필요한 것이다.

동기화 객체의 사용에 대해서는 책에 많이 나오니깐 설명은 생략한다.

단지 동기화 객체를 어떻게 사용해야 성능향상을 꾀할수 있는가가 중요한 문제다.
멀티스레드 동기화를 쓰다보면 우리는 아래와 같은 문제에 종종 접하게 된다.

MyObject obj[100];

Thread()
{
     ....
     criticalsection.Lock();

     obj[n].DoSomething();

     criticalsection.Unlock();
     ....
}

Thread() 가 10개 실행되고 있다고 했을때..
우리는 obj 가 뒤죽박죽이 되는걸 막기위해 크리티컬 섹션으로 obj 접근을 보호했다.
따라서 10개중 크리티컬 섹션에 진입한 하나의 스레드만이 obj에 접근하도록 했다.
이제 obj 는 여러개의 스레드로 부터 안전하며 동기를 유지할수 있게 되었다.

그러면 그것으로 끝났는가?

여기에는 치명적인 성능결함이 있다. 즉 obj 의 DoSomething() 이 한번에 하나밖에
호출되지 않는다는 점이다.

당연한거 아니냐고?

만약 우리가 CPU 10개를 가지고 있다고 치자. 10개의 스레드는 단순한 이론적으로
모두 동시에 실행되고 있다. 그러나 정작 DoSomething() 은 동시에 실행되지 못한다.
CPU가 10개든 100개든 마찬가지다. 
만약 obj[n] 의 n이 같은 번호를 가진 스레드가 여러개라면 그것들 끼리는 철저히
동기가 되어 한번에 하나만 DoSomething()이 호출되어야 하지만, n이 다른 것들은?
가령 Thread() 1,5,6 번은 n=4 이고 Thread() 2번은 n=1이고 Thread()3번은 n=5라고
했을때, Thread() 2번과 Thread()3번은 Thread() 1,5,6 번과 전혀 상관이 없는
객체를 엑세스 하고 있기 때문에 동시에 실행되어도 상관없다.
즉 1,5,6번 스레드만 동기화 되면 되는데 괜히 2번과 3번도 실행되지 못하는 사태가
벌어지고 있는것이다.
이게 무슨 멀티스레드냐. 
차라리 스레드 안쓰고 while 로 10번 돌지.
괜히 스레드 끼리 컨텍스트 스위칭 한다고 CPU만 축내고 속도만 느려진다.

그/래/서!


*코드를 동기화 하지 말고 자원을 동기화 하라.

위의 예와 같이 멀티스레드에서 코드를 동기화 하면 모든 스레드가 코드 진입을
못하기 때문에 (적어도 객체가 여러개일때)특정 객체가 동기화 되어야 한다면 
객체가 각각 동기화 되어야 한다는 것이다.

이 예를 위 예제를 수정하여 보도록 하자.

class obj
{
     public:
        DoSomething();
        criticalsection_type m_cs;        <-- 이것.
};

Thread()
{
     ....
     obj[n].m_cs.Lock();

     obj[n].DoSomething();

     obj[n].m_cs.Unlock();
     ....
}

위 예제에서 obj 클래스는 크리티컬섹션 객체를 멤버로 가지고 있다.
그리고 Thread() 에서는 각 객체가 가진 크리티컬섹션을 이용해 객체를 보호하고 있
다.
이렇게 함으로서 앞에서 언급한 서로다른 객체로의 접근에서도 모든 스레드가 동시에
실행되지 못하는 문제를 해결할수 있다.
그리고 정확히 같은 객체를 엑세스 하는 스레드 끼리만 동기화 되고 아닌경우는
모두 동시에 실행될수 있게 되었다.

이제서야 멀티스레드를 쓰는 이점을 제대로 살린 셈이다.


* 또다른 의문.
  
우리는 이런 의문에 빠질지도 모른다. C런타임 함수들은 과연 스레드로 부터
안전하게 엑세스 되는가? 
토큰을 추출하는 strtok() 함수는 내부적으로 전역변수를 사용한다.
그러면 멀티스레드에서 내부적으로 전역변수를 사용하는 C런타임 함수들을
사용하기 위해 모두 크리티컬섹션으로 보호해야 하는가?
음.. 보호해야 정상이긴 하다.
그러나 그렇게 까지 하기엔 코딩이 너무 가혹하다. 
그래서 VC++ 은 C런타임 함수의 버전을 2가지로 관리한다고 한다.
첫번째가 싱글스레드 용이요, 둘째가 멀티스레드용. 
아주 앞쪽에서 이야기 했지만 우리는 project->setting->C/C++ 의 Code generation
탭에서 멀티스레드 사용의 옵션을 선택할수 있다.
그 옵션이 선택되면 빌드타임에서 링커가 알아서 멀티스레드용 C런타임 함수를
링크한다. 따라서 결론은 그런 걱정은 붙들어 메시라는 거다.
멀티스레드용 C런타임 함수들은 스레드 스택에 그 전역변수를 저장하게 함으로써
멀터스레드에서도 변수가 공유되지 않도록 보장한다.
참으로 다행스러운 일이다.


* 스택의 위험.

지금까지는 그냥 지나왔지만 스레드를 생성하는 함수는 꼭 스택사이즈를 파라미터로
같고 있다. 대게의 경우 0을 넣는데 그러면 디폴트 1M 크기의 스레드 스택이 생긴다.
그러나 디폴트가 1M 인것은 컴파일러 옵션에서 1M를 잡았기 때문이다.
그 디폴트 크기를 변경하고 싶을때는 project setting 의 Link 에 stack 
allocations 에서
변경할수 있다. 그러나 변경하지 않는게 좋을듯 하다. 스택에 대한 관리는 95계열의 
OS와
NT 계열의 OS가 약간 틀린데 COMMIT 하는 스택의 양도 틀리고 위치도 틀리다.
대게 건드리지 않는게 수명연장에 도움이 될것 같다. 스택에 집어넣을게 많거든 
그냥 Heap 에 넣어라.

문제는 스택오버플로우나 스택언더플로우를 조심해야 한다는것이다.
NT 든 95시리즈든 스택오버플로우나 언더플로우를 막기위해 나름데로의 디자인을 가
지고
있다. 그리고 만약 주어진 스택의 경계를 넘어설때는 예외가 발생하고 또 심한 경우
에는
엑세스 위반이 일어난다. 문제는 우리가 스택의 경계를 넘지 않도록 스스로 주의해
야 한다는
점이다. 스택의 크기가 1M 라면 Intel CPU의 경우 페이지의 크기는 4k. 따라서 정확

256개의 메모리페이지가 사용된다. NT의 경우 그중 페이지2개는 스택의 상위와 하위

표현하기 위해 사용되고 95시리즈의 경우 아래위로 64k 씩 오버/언더 체크 페이지가 

할당된다. 뭐 골치아픈 원리로 인해 스택에서 메모리사용이 증가되어 오버/언더를 칠

앞서 말한것 처럼 예외나 에러가 나기는 하지만 진짜 무서운 것은 에러가 나지 않고
소리소문 없이 프로세스를 종료시키게 될수 있다는 것이다. 또 스택의 오버/언더를 
체크
하는 범위를 훌쩍 뛰어넘어(NT는 양끝의 4k페이지, 95시리즈는 양끝64k페이지) 엑세
스 
하려고 하면 결과는 뻔하다. OS 는 멍청하게 있을것이고 엑세스된 메모리는 보기좋게
손상된다. 따라서 스택의 한계에 점점 다다르면서 엑세스가 될때는 예외나 에러로 끝
나지만
그렇지 않고 훌쩍 뛰어넘어 엑세스 하는 코드를 만들면 그야말로 원인도 모르는
버그를 만들기 십상이라는 거다. 
결론은 하나밖에 없다.
스택에서 스스로 조심하라는 거다. 1M  넘지 않도록. 또 포인터 조심하고..

 

 

 

=======================

=======================

=======================

 

 

 

 

출처 :http://blog.naver.com/xtelite/50023358990

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;

}

 

 

 

 

=======================

=======================

=======================

 

 

 

 

출처: http://openshareit.tistory.com/entry/MFC-Thread

 

// 전역함수 선언 부분
UINT Ping(LPVOID pParam);     

// 전역변수 선언 부분
CWinThread *pThread;

void CWinpingDlg::OnBnClickedOk()
{
  char* szStr = "test";
  
  // Ping 스레드를 생성하여 전역변수 pThread에 넘겨준다.
  // szStr 변수를 void 포인터로 넘겨준다.
  ::pThread = AfxBeginThread( Ping, (LPVOID)szStr ); 
}

UINT Ping(LPVOID pParam)
{
  // void 포인터형 pParam을 받아서 szStr 포인터에 넘겨준다.
  char *szStr = (char *) pParam;
  
  return 0;
}

// AfxBeginThread로 선언한 스레드는 return 0을 만나면 스레드를 제거한다.

[출처]http://www.npteam.net/61 by TTF 

출처: http://openshareit.tistory.com/entry/MFC-Thread [Open AR]

 

 

 

=======================

=======================

=======================

 

 

 

 

출처: http://blog.naver.com/PostView.nhn?blogId=skyarro&logNo=120095695053&categoryNo=54&viewDate=¤tPage=1&listtype=0

 

Thread

1. Thread

프로세스 내에서 실행되는 각각의 작업 또는 실행 경로.

프로세스 내에서 실행되는 세부 작업단위(여러 개의 Thread가 모여서 하나의 프로세스를 이룸)

수행 중 또 다른 수행을 할 수 있습니다.

프로세스는 실행 파일이 실행 될 때 만들어지며 운영체제는 내부적으로 이러한 프로세스를 여러 논리적 코드로 분할하여 관리하게 됩니다.

이 단위를 Thread라고 합니다.

프로세스는 반드시 하나 이상의 Thread가 생성되어 실행됩니다.

프로세스는 동시에 움직이지는 않고 서로 병행적으로 움직입니다.

멀티 태스킹은 여러 개의 프로세스를 번갈아 가면서 수행하는 것이고 멀티 Thread는 하나의 프로그램을 여러 개의 기능으로 나누어 이를 동시에 실행시키는 것을 의미 합니다.

 

Thread 종류

1) 사용자 인터페이스 스레드메시지를 받을 수 있는 스레드로 프로그램의 실행과는 독립적으로 메시지를 받아서 수행하는 스레드

탐색기가 대표적인 예

 

2) 작업자 스레드백그라운드 작업을 수행하는 스레드로 메시지를 받을 수 없는 스레드

Thread를 사용한 경우와 그렇지 않은 경우의 차이

1. MFC 프로젝트 작성(SDI 옵션 프로젝트명 Thread)

2. 전역 함수로 작성(APP 클래스의 빈 부분에 작성하면 됩니다.)



UINT MyThreadFunc(LPVOID lparam)
{
        RECT rect;
        CDC *pDC;
        CString m_str;
        CThreadView *pView;

        pView=(CThreadView*) lparam;
        pView->GetClientRect(&rect);

        pDC=pView->GetDC();

        for(int i =0; i<40; i++)
        {
               pDC->Rectangle(i*20,10, i*20+20,30);
               Sleep(500);
        }

        pView->ReleaseDC(pDC);
        return 0;
}

3. View 클래스의 마우스 왼쪽 버튼 클릭 메시지 작성

        MyThreadFunc((LPVOID)this);

 

4. 위의 메시지 수정

        AfxBeginThread(MyThreadFunc,(LPVOID)this);

2. 작업자(Worker) Thread

AfxBeginThread함수를 호출하기만 하면 됩니다.

CWinThread* AfxBeginThread(Thread로 동작할 함수부가정보우선순위스택 사이즈실행여부보안을 위한 설정);

스레드로 동작할 함수는 전역으로 선언하거나 static 함수로 선언되어야 합니다.

부가정보는 일반적으로 LPVOID pParam으로 임의의 4바이트 변수를 넘겨 줄 수 있습니다.

만일 4바이트 이상의 부가정보를 넘겨주고자 하는 경우는 구조체를 생성해서 넘겨주면 됩니다.

우선 순위는 실행의 우선 순위입니다.

스택 사이즈는 0을 주면 1MB까지 스택을 사용할 수 있게 되고 다른 사이즈를 지정하면 그 크기만큼 스택의 사이즈를 사용할 수 있습니다.

실행여부는 0을 지정하면 바로 실행되고 CREATE_SUSPENDED를 지정하면 Thread가 생성만 되고 ResumeThread를 만날 때까지 실행을 멈추고 있게 됩니다.

이 함수는 정상적으로 수행되면 Thread의 주소를 리턴합니다.

만일 Thread를 중지하거나 계속 수행종료 등을 하고자 한다면 Thread의 주소를 받는 변수를 만들어서 사용해야 합니다.

전역 함수 GetCurrentThreadId()를 이용하면 현재 수행 중인 스레드의 ID를 알 수 있습니다.

 

Thread에 사용하는 함수

ð  GetThreadPriority(): 우선순위 값 리턴

ð  SetThreadPriority(int nPriority): 우선순위 변경

THREAD_PRIORITY_TIME_CRITICAL

THREAD_PRIORITY_HIGHEST

THREAD_PRIORITY_ABOVE_NORMAL

THREAD_PRIORITY_NORMAL

THREAD_PRIORITY_BELOW_NORMAL

THREAD_PRIORITY_LOWEST

THREAD_PRIORITY_IDLE

ð  SuspendThread(): Thread 중지

ð  ResumeThread(): Thread 재 실행

멤버 변수 중에서 m_hThread Thread의 핸들을 리턴합니다.

 

사용방법

CWinThread * Thread 연결할 변수 = AfxBeginThread();

변수명->함수 호출

Thread의 종료

강제 종료

TerminateThread(Thread핸들, 0);

 

스레드가 수행할 함수에게 넘겨주는 매개 변수에 flag 변수를 하나 설정하여 그 값이 TRUE일 때만 수행하게 하고 주 프로세스에서 그 값을 FALSE로 변경하게 해서 종료 시킬 수 있습니다.

 

WaitForSingleObject()사용

DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);

hHandle: 스레드 핸들

dwMilliseconds:1/1000초 단위 시간

 

리턴값

WAIT_OBJECT_0: 스레드의 수행이 종료

WAIT_TIMEOUT: 현재 스레드가 수행 중 임

 

위의 함수를 이용하면 스레드가 종료된 후 다른 작업을 수행할 수 있으며 스레드의 현재 상태도 조사가 가능합니다.

 

여러 개의 스레드의 상태 조사

WaitForMultipleObjects(스레드 핸들 개수핸들 배열의 주소, TRUE이면 모든 스레드의 상태이고 FALSE이면 아무 스레드나 한 개의 상태,시간사용할 메시지에 따른 옵션)

리턴 값

WAIT_OBJECT_0 + nCount: 몇 개가 신호를 받았는지 여부

WAIT_TIMEOUT: 수행 중인 경우

 

Thread의 현재 상태 알아보기

CString temp;

DWORD dwExitcode=0;

GetExitCodeThread(Thread핸들->m_hThread, &dwExitcode);

temp.Format(TEXT("%d"), dwExitcode);

AfxMessageBox( temp );

Thread 예제(하나의 매개변수만 넘겨주는 경우)

Thread 예제(2개 이상의 매개변수를 넘겨주는 경우)

View 클래스의 마우스 중간 버튼 클릭 메시지에 작성

 

3. 멀티 Thread

2개 이상의 스레드가 동시에 수행되는 경우를 멀티 스레드라고 합니다.

 

Thread의 우선 순위 변경 예제

여러 개의 Thread를 생성해서 돌리는 예제 (배너 광고)

멀티 Thread의 문제점

1. SDI 옵션으로 프로젝트 생성(MultiThread)

2. 전역 변수를 선언하고 2개의 Thread 함수 작성

 

 

int X;
UINT ThreadFunc1(LPVOID Param)
{
        CDC *pDC;
        CMultiThreadView *pView;
        pView=(CMultiThreadView*)Param;
        pDC=pView->GetDC();

        for (int i=0;i<100;i++)
        {
               X=100;
               Sleep(1);
               pDC->TextOut(X,100,TEXT("Hello"),5);
        }

        return 0;
}


UINT ThreadFunc2(LPVOID Param)
{
        CDC *pDC;
        CMultiThreadView *pView;
        
        pView=(CMultiThreadView*)Param;
        pDC=pView->GetDC();

        for (int i=0;i<100;i++)
        {
               X=200;
               Sleep(1);
               pDC->TextOut(X,200,TEXT("World"),5);
        }

        return 0;
}


3. View클래스의 LButtonDown에 작성
        AfxBeginThread(ThreadFunc1,(LPVOID)this);
        AfxBeginThread(ThreadFunc2,(LPVOID)this);

 

멀티Thread를 제어하는 방법

임계영역(CriticalSection), 상호배제(Mutex), 이벤트 처리(Event), 세마포어(Semaphore) 등이 있습니다.

afxmt.h 파일에 모두 존재하므로 위의 기능을 사용하고자 하는 경우 이를 include 해 주어야 합니다.

1)임계영역(CriticalSection),

공유자원을 사용하는 코드 영역

하나의 Thread가 임계 영역에 접근할 경우 Thread는 다른 Thread가 이 영역에 접근할 수 없도록 이 영역에 Lock을 걸어 주며 임계 영역의 수행이 끝나게 되면 Lock을 해제해서 다른 Thread가 임계영역에 접근할 수 있도록 제어하게 됩니다.

 

멤버 함수

Lock()

Unlock()

 

1. stdafx.h 파일에 헤더파일 추가

#include <afxmt.h>

 

2. 다음의 변수를 추가

전역 변수로 추가

CCriticalSection g_cs;

 

3. 공통되는 자원을 사용하는 코드 영역을 다음 2개의 함수로 묶음

g_cs.Lock();

g_cs.Unlock();

 

2) CMutex

CMutex 클래스를 사용해도 동일한 효과를 얻을 수 있습니다.

일반적으로 CMutex는 여러 개의 프로그램(DLL)이 하나의 자원을 공유하는 경우에 사용합니다.


 

3) CEvent 이용

CEvent(BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL,

LPSECURITY_ATTRIBUTES lpsaAttribute = NULL);

번째 값은 처음 초기화 될 때의 신호 여부를 의미하는데 FALSE이면 Event가 설정되지 않은 상태로 생성됩니다.

2번째 값은 한번 설정된 값을 계속 유지할 것인지 여부를 지정하는 옵션으로 기본값은 FALSE인데 이렇게 되면 WaitForSingleObject()가 계속해서 WAIT_OBJECT_0을 반환합니다.

TRUE이면 자동으로 모든 Lock()을 해제합니다.

lpszName은 이름입니다.

마지막은 보안속성입니다.

공통 코드영역에서 이벤트 오브젝트.PulseEvent(); 를 설정해서 이 코드를 만나면 이벤트를 발생시킵니다.

이벤트 오브젝트.Lock();을 이용해서 다른 코드 영역을 묶어 줍니다.

이벤트의 이름을 적절히 활용하면 다른 프로세스의 제어가 가능합니다.

 

4) CSemaphore

CSemaphore(LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL,

LPSECURITY_ATTRIBUTES lpsaAttributes = NULL);

lInitialCount: 처음에 수행할 스레드 개수

lMaxCount: 최대 스레드 개수

pstrName: 세마포어 객체의 이름

lpsaAttributes: 보안 속성으로 NULL이면 생성자 스레드의 권한을 가짐

스레드가 2개 이상이 동시에 수행되어야 할 때 사용하는 클래스입니다.

초기값과 최대값은 일반적으로 동일 한 값을 주면 됩니다.

Lock() Unlock()멤버 함수를 이용해서 공통 코드 영역을 묶어 주면 됩니다.

 

5) CMultiLock

여러 개의 이벤트가 발생해야만 수행되는 상호배제를 만들 때 사용

CEvent g_Ev[개수];

CMultiLock mLock(g_Ev, 개수);

m_Lock.Lock()으로 묶으면 개수 만큼의 이벤트가 발생해야 만 해제 됨

m_Lock.Lock(INFINITE, 개수)로 묶으면 한 개라도 이벤트가 발생하면 해제 됨


 

예제) CSemaphore 사용

예제)CEvent를 이용한 여러 개의 프로그램 종료(다른 프로그램에 종료)

3. UI Thread

메시지 루프를 가지고 있어서 사용자의 입력이나 메시지 형태로 전달되는 이벤트를 처리할 수 있는 Thread

자체적으로 메시지 루프를 갖기 때문에 메시지를 받을 수 있어서 PostThreadMessage(메시지 종류, WPARAM, LPARAM)를 이용해서 메시지를 전달받아서 수행 할 수도 있습니다.

InitInstance 함수에서 윈도우를 생성해서 별도의 윈도우를 제어할 수 도 있습니다.

 

1) 생성

CWinThread 클래스로부터 상속받는 클래스 생성

 

2) 원하는 함수 재정의(Run 메소드 재정의)

Run 메소드가 스레드가 수행 할 내용을 정의

 

3) UI Thread의 실행

CWinThread * AfxBeginThread(

CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);

스레드의 주소를 리턴함

 

pThreadClass: 스레드로 수행할 클래스 이름

nPriority: 스레드 우선 순위

nStackSize: 스택 크기

dwCreateFlags: 초기 실행 여부

lpSecurityAttrs: 보안을 위한 옵션

 

예제)

예제)UI 스레드에서의 상호배제 문제

예제는 첨부문서픞 참조하세요

Visual C++ MFC - chapter 19.MFC 스레드

 

 

 

=======================

=======================

=======================

 

 

 

 

출처:  http://egloos.zum.com/spaurh/v/4462879

API Thread사용법

 

1. 개요 

현재 대부분의 OS는 프로세스 스케쥴링에 의해 프로그램의 멀티태스킹(Multi-tasking)을 지원하고 있다. 
멀티태스킹이란 실행되고있는 프로그램을 일정 단위로 잘라서(slice) 순서대로 CPU를 사용하게끔 하는 것 인데, 
사용자는 마치 동시에 여러 개의 프로그램이 실행되는 것처럼 느낄 수 있게 된다. 
즉, CPU 사용률을 최대화 하고, 대기시간과 응답시간의 최소화를 가능케 해주는 방법이다. 

이번에는 프로세스 한 개만 놓고 보자. 
한 프로세스는 구성면에서 [텍스트]-[데이터]-[스택] 영역으로 구성되어있고, 기능면에서는 텍스트의 모듈들은 각각의 역할을 가지고 있다. 
프로세스에서의 공유메모리영역을 제외한 부분끼리 묶어서 쓰레드로 만든 후, 이것들을 멀티태스킹처럼 동작시키면 멀티쓰레딩이 되는 것이다. 

멀티쓰레드 프로그램을 작성할 경우의 장점은 다음처럼 요약될 수 있다. 
1) 병렬화가 증가되어 
2) CPU사용률이 극대화되며, 
3) 전체적 처리율이 빨라지고, 
4) 사용자에대한 응답성이 향상된다. 
5) 또한, 완벽에 가까운 기능별 구분에 의한 모듈작성을 함으로써 설계가 단순해져서, 
6) 프로그램의 안정성이 향상된다. 
7) 코드의 복사본을 여러 개 수행하여 여러 개의 클라이언트에서 동일한 서비스를 제공할수 있다. 
8) 블록될 가능성이 있는 작업을 수행할 때 프로그램이 블록되지 않게 한다. 

하지만, 쓰레드를 사용하면 오히려 불리한 경우도 있다. 대표적인 예로, 교착상태(deadlock)와 기아(starvation)이다. 
쓰레드 기법을 사용할 때 주의사항을 정리하자면, 
1) 확실한 이유를 가지고 있지 않는 경우에는 쓰레드를 사용하면 안 된다. 즉 쓰레드는 명확히 독립적인 경우에 사용해야 한다. 
2) 명확히 독립적인 쓰레드라 하여도 오히려 나눔으로 인해 OS가 쓰레드를 다루는데에 따른 부하(overload)가 발생하게 된다. 
즉, 실제 쓰레드에 의해 수행되는 작업량보다 클 경우에는 사용하지 않도록한다. 

멀티쓰레드를 이용한 애플리케이션을 작성하는 구조에는 3가지 방법이 있다.. 
1. boss/worker 모델.. 
2. work crew 모델. 
3. pipeline 모델. 

1. 첫번째 쓰레드(주쓰레드)가 필요에 따라 작업자 쓰레드를 만들어 내는 경우. 
이런 경우는 C/S 환경에서 접속받는 부분을 쓰레드로 돌리고, 접속요청이 오면 새로운 쓰레드를 만들어 사용자와 연결시켜 주는 방법이다. 
이때 접속 받는 쓰레드가 주 쓰레드(boss Thread) 라고 하고, 사용자와 연결된 다른 쓰레드.. 
즉 주 쓰레드로부터 실행된 쓰레드는 작업자 쓰레드(worker Thread) 라고 한다.. 
2. 두번째 방식은 어떤 한 작업을 여러 개의 쓰레드가 나눠서 하는 방식이다. 
즉 집을 청소한다는 개념의 작업이 있으면, 청소하는 작업에 대한 쓰레드를 여러 개 돌리는 거.. 
3. 공장라인을 생각... 

쓰레드는 UI(User Interface) Thread와 Worker(작업자) Thread로 나뉜다. 
UI Thread는 사용자 메시지 루프를 가지고 있는(즉 어떤 메시지가 날라오면 일하는.. )쓰레드이고.. 
Worker Thread는, 보통 오래 걸리는 작업이나 무한루프를 가지는 작업을 하는 사용자 정의 함수의 경우 사용. 
UI Thread를 사용하려면, CWinThread 파생 클래스를 만들어 사용한다. 

MFC에서는 AfxBeginThread의 서로 다른 버전 두 개를 정의 하고 있다.. 
하나는 작업자 쓰레드를 위한 것이고, 하나는 UI쓰레드를 위한 것이져.. 

원형은 다음과 같다.. 
UINT ThreadFunc(void* pParam) 

이함수는 정적(static)클래스 멤버 함수 이거나 클래스 외부에서 선언한 함수여야 한다. 


2. 쓰레드의 기본 

1) 쓰레드 생성 
WM_CREATE 에서 쓰레드를 만들면 되는데 함수는 다음과 같다. 
HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, 
LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, 
DWORD dwCreationFlags, LPDWORD lpThreadId); 

+lpThreadAttributes : 쓰레드의 보안속성 지정. 자식 프로세스로 핸들을 상속하지 않은 한 NULL 
+dwStackSize : 쓰레드의 스택 크기 지정. 안정된 동작을 위해 쓰레드마다 별도의 스택 할당. 
0으로 설정하면 주 쓰레드(CreateThread를 호출한 쓰레드)와 같은 크기를 갖으며, 스택이 부족할 경우 자동으로 스택크기를 늘려주므로 0으로 지정하면 무리가 없다. 
+lpStartAddress : 쓰레드의 시작함수를 지정. 가장 중요한 인수. 
+lpParameter : 쓰레드로 전달할 작업 내용이되 인수가 없을경우 NULL임. 
+dwCreationFlags : 생성할 쓰레드의 특성 지정. 0이면 아무 특성없는 보통 쓰레드가 생성되고 
CREATE_SUSPENDED 플래그를 지정하면 쓰레드를 만들기만 하고 실행은 하지 않도록하고 실행을 원하면 ResumeThread함수를 호출하면 된다. 
+lpThreadId : 쓰레드의 ID를 넘겨주기 위한 출력용 인수이므로 DWORD형의 변수 하나를 선언한 후 그 변수의 번지를 넘기면 됨. 

**** 작업자 쓰레드 생성하기 **** 

작업자 쓰레드로 특정한 작업을 하는 사용자 정의 함수를 맹글기 위해서, 윈도우에서는 여러가지 쓰레드 생성 함수를 제공해 준다. 
그 함수의 종류를 알아보도록 하져.. 

1. CreateThread() 
2. _beginthread(), _beginthreadex() 
3. AfxBeginThread(), AfxBeginThreadEx() 

이렇게 약 5가지의 쓰레드 생성함수가 존재한다. 
이제부터 저 5가지 함수의 특징을 알아보도록 하져….. 
그럼 첫번째 CreateThread()함수. 이 함수는 보통 사용할때 다음과 같이 사용한다. 

HANDLE handle; 
Handle = CreateThread( Threadfunc(), Param ); 

첫번째 인자는 사용자가 쓰레드로 돌려야할 작업함수를 써주는 곳이고, 두번째는 작업함수에 인자값으로 전해줄 값이 들어간다.. 
이 인자값 형은 VOID*으로 되어 있기 때문에 4BYTE 이내의 값은 어떤 값이든 들어갈수 있져..대신 TYPE CASTING을 해주어야 하져.. 
그리고 받는 쪽에서도 type casting를 해서 받아야 한다. 
이함수가 올바르게 실행이 되면 쓰레드에 대한 핸들을 반환하는데.. 이 핸들을 가지고 쓰레드를 조작할 수가 있져.. 
대표적으로 쓰레드를 닫을 때 CloseHandle()함수를 사용해서 쓰레드 핸들을 넣어주고 쓰레드를 닫아 주어야 한다.. 
이함수로 생성된 쓰레드를 닫을때는 ExitThread() 면 됩니다. 

그럼..두번째 _beginthread를 알아보도록 하져..CreateThread는 쓰레드에서 win32 API함수만 호출할수 있다.. 
즉, 사용자가 어떤작업을 하는 함수를 만들 때 그 함수 안에서 win32API만 사용할수 있다는 말이다.. 
즉 C함수나 MFC는 저얼대~~ 못 쓴다…. 
_beginthread 함수는 win32 API아 C 런타임 함수를 사용할 때 사용한다. 
이 함수를 사용하면 C런타임 라이브러리가 핸들을 자동으로 닫으므로 이를 직접할 필요는 없다. 
대신 _beginthreadex는 스레드 핸들을 직접 닫아야 한다. 그리고 이 쓰레드를 닫을 때는 _endthread(), _endthreadex()를 사용하면 된다. 

세번째 AfxBeginThread()와 AfxBeginThreadEx().. 
실질적으로 가장 자주 사용하는 쓰레드 생성함수이다.. 
이 함수를 이용하면 사용자 정의 함수내에서 MFC, win32 API, C 런타임 라이브러리등 여러가지 라이브러리 함수들을 전부 사용할수 있다.. 
주로 프로젝트를 MFC로 만들 때 사용하죠.. 
이 함수는 리턴값이 CWinThread* 형을 리턴하며, 이 함수와 매칭되는 종료함수는 AfxEndThread()이다… 
해서 쓰레드가 종료되면 MFC는 쓰레드 핸들을 닫고 리턴값으로 받은 CWinThread*객체를 제거한다. 

CWinThread* pThread = AfxBeginThread( Threadfunc, &threadinfo ); 

첫번째 인자는 사용자 정의 함수이고, 두번째는 첫번째 인자의 쓰레드 함수에 인자값으로 들어갈 파라미터이다.. 
이 형은 void* 형으로 4byte를 가지므로 어떤 형으로 넣어줄 때 type casting하면 된다…. 

그 예는 다음과 같다. 

int nNumber = 1000; 

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

UINT ThreadFunc(LPVOID pParam) 

int j = (int)pParam; 
for (int i=0; i<j; i++) 

// 수행할 작업 




작업자 스레드 함수에 4바이트 이상의 정보를 넘겨주어야 할 경우에는 
다음과 같이 작업자 스레드 함수에 넘겨주어야 할 모든 값을 포함하는 구조체를 선언하고, 

typedef struct tagTREADPARAMS { 
CPoint point; 
BOOL *pContinue; 
BOOL *pFriend; 
CWnd *pWnd; 
} THREADPAPAMS; 

// 그런 다음 구조체에 필요한 값들을 설정하고, 이 구조체의 포인터를 넘겨준다. 
THREADPAPAMS *pThreadParams = new THREADPAPAMS; // new로 할당 
pThreadParams->point = m_ptPoint; 
pThreadParams->pContinue = &m_bExec; // 쓰레드 실행 플래그 
pThreadParams->pFriend = &m_bYield; // 쓰레드 양보 플래그 
pThreadParams->pWnd = this; 
m_pThread = AfxBeginThread(ThreadFunc, pThreadParams); 

UINT ThreadFunc(LPVOID pParam) 

// 넘어온 인자를 복사 
THREADPAPAMS *pThreadParams = (THREADPAPAMS *)pParam; 
CPoint point = pThreadParams->point; 
CWnd *pWnd = pThreadParams->pWnd; 
BOOL *pContinue = pThreadParams->pContinue; 
BOOL *pFriend = pThreadParams->pFriend; 
delete pThreadParams; // delete로 해제 

// "실행" 플래그가 TRUE인 동안 스레드가 실행됨 
while(*pContinue) 

// 수행할 작업 

// "양보" 플래그가 TRUE이면 다른 스레드에 CPU를 양보 
if(*pFriend) Sleep(0); 

return 0; 



자 그럼..정리해 보도록 하져…..쓰레드를 생성하는 함수들은 크게 3가지가 있고..(확장된것까지 생각하면 5개..^^ ) 이들 함수의 특징은 다음과 같다. 

쓰레드가 win32 API만을 사용한다면 CreateThread()를 사용하면 되고, C런타임 라이브러리를 사용하다면 _beginthread()를 사용하고, 
전부다 사용한다면 AfxBeginThread()를 사용하면 된다. 


2) 쓰레드 종료 
작업 쓰레드가 종료되었는지 조사하는 함수는 다음과 같다. 
BOOL GetExitCodeThread(HANDLE hThread, PDWORD lpExitCode); 

+hThread : 쓰레드의 핸들 
+lpExitCode : 쓰레드의 종료코드. 
+Return : 계속 실행중 : STILL_ACTIVE, 쓰레드 종료 : 스레드 시작함수가 리턴한 값 or ExitThread 함수의 인수 

쓰레드가 무한루프로 작성되어 있다해도 프로세스가 종료되면 모든 쓰레드가 종료되므로 상관이 없다. 
백그라운드 작업을 하는 쓰레드는 작업이 끝나면 종료되는데 때로는 작업도중 중지해야 할 경우에는 다음 두 함수가 사용된다. 

VOID ExitThread(DWORD dwExitCode); 
BOOL TerminateThread(HANDLE hThread, DWORD dwExitCode); 

ExitThread는 스스로 종료할 때 사용.인수로 종료코드를 넘김. 종료코드는 주 쓰레드에서 GetExitCodeThread함수로 조사할 수 있다. 
이것이 호출되면 자신의 스택을 해제하고 연결된 DLL을 모두 분리한 후 스스로 파괴된다. 

TerminateThread는 쓰레드 핸들을 인수로 전달받아 해당 쓰레드를 강제종료시킨다. 
이 함수는 쓰레드와 연결된 DLL에게 통지하지 않으므로 DLL들이 제대로 종료처리를 하지 못할 수 있고 리소스도 해제되지 않을 수 있다. 
그래서 이 작업 후  어떤일이 발생할지를 정확히 알때에만 사용하도록한다. 


스레드를 죽이는 방법엔 두가지가 있져.. 
1. 스레드 내부에서 return을 시킬 때. 
2. AfxEndThread를 호출할 때. 
안전한 방법은 스레드 내부 자체에서 return문을 이용해서 죽여주는게 안전하다. 위의 예와 같이... 

다음은 쓰레드를 종료하는 함수의 예이다. 
if(m_pThread != NULL) 

HANDLE hThread = m_pThread->m_hThread; // CWinThread *m_pThread; 
m_bExec = FALSE; // 실행 플래그를 FALSE로 하여 쓰레드 종료시킴.. 
::WaitForSingleObject(hThread, INFINITE); 
// 이후 정리작업... 



위의 첫번째 방법과 같이 return을 받았을때는 GetExitCodeThread를 이용해서 검색할수 있는 32bit의종료 코드를 볼수 있다.. 

DWORD dwexitcode; 
::GetExitCodeThread( pThread->m_hThread, &dwExitCode ); 
// pThread는 CWinThread* 객체의 변수.. 

만약 실행중인 스레드를 대상으로 저 코드를 쓰게 된다면 dwExitCode에는 STILL_ACTIVE라는 값이 들어가게 된다. 


근데..위의 코드를 사용함에 있어 제약이 좀 있다. 
CWinThread*객체는 스레드가 return 되어서 종료가 되면 CWinThread객체 자신도 제거되어 버린다..즉 동반자살이져.. 
delete시켜주지 않아도 메모리에서 알아서 없어진다는 말이져.. 
즉…return이 되어서 이미 죽어버린 스레드를 가지고 pThread->m_hThread를 넣어주면, Access위반이란 error메시지가 나오게 되져.. 

이런 문제를 해결할라면 CWinThread* 객체를 얻은 다음 이 객체의 멤버 변수인 m_hAutoDelete를 FALSE로 설정하면 
스레드가 return을 해도 CWinThread객체는 자동으로 제거 되지 않기 때문에 위의 코드는 정상적으로 수행이 된다.. 

이런 경우에 CWinthread*가 더 이상 필요가 없어지면 개발자 스스로 CWinThread를 delete시켜 주어야 한다.   

또다른 방법으로 스레드가 가동이 되면 CWinThread*의 멤버변수인 m_hThread를 다른 곳으로 저장을 해놓고 
이 것을 직접GetExitCodeThread()에 전달을 하면 그 쓰레드가 실행중인지 한때는 실행되고 있었지만 죽어버린 스레드인지 확인이 가능하다. 

int a = 100;              // 파라미터로 넘겨줄 전역변수. 
CWinThread* pThread   // 전역 쓰레드 객체의 포인터 변수. 
HANDLE threadhandle;  // 스레드의 핸들을 저장할 핸들변수. 

Initinstance() // 프로그램초기화. 

// 프로그램 실행과 동시에 스레드 시작. 
1번방법:pThread = AfxBeginThread( func, (int) a ); 

// 스레드가 리턴되면 자동으로 CWinThread객체가 자동으로 파괴되지 않게 설정. 
2번방법:pThread->m_hAutoDelete = FALSE; 

// 쓰레드 핸드를 저장. 위의 m_hAutoDelete를 설정하지않았을경우.. 
threadhandle = pThread->m_hThread; 


MessageFunction()  // 어떤 버튼을 눌러서 스레드의 상태를 알고 싶다.. 

char* temp; 
DWORD dwExitcode; 
// 스레드 객체의 m_hAutoDelete를 fasle로 설정해서 스레드가 return되어도 
// 객체가 자동으로 파괴되지 않아서 핸들을 참조 할수 있다. 
1번방법: ::GetExitCode( pThread->m_hThread, &dwExitcode); 

// 스레드가 종료되고 미리 저장해둔 핸들을 이용할경우.. 
2번방법:::GetExitCode(threadhandle, &dwExitcode); 
sprintf( temp, "Error code : %d", dwExitcode ); 

// 스레드 객체 삭제.. 
1번방법: delete pThread; 
AfxMessageBox( temp ); 


func( void* pParam ) 

int b = (int) pParam; 
for( int i = 0; i < b; i++) 

// 어떤일을 한다. 

return;  // 작업이 끝나면 리턴한다. 이때 스레드 자동으로 종료. 



1번째 방법은 스레드를 생성하고 m_hAutoDelete를 false로 해서 
스레드가 return해서 자동종료해도 CWinthread를 자동파괴하지 않게 하고, GetExitCodeThread()를 호출하져.. 
밑에서 delete해 주는 거 꼭 해야되고요..안그럼 메모리 누수가 되져.. 

2번째는 m_hThread를 다른 핸들변수에 저장해 놓고..스레드가 return되면 CWinThread*도 같이 파괴가 되는데.. 
원래 저장한 핸들을 가지고 GetExitcodeThread()를 호출해서 한때 존재했지만 종료된 쓰레드를 검사하는 것이져….이해 OK????? 


3) 대기 함수 

WaitForSingleObject(), WaitForMultipleObjects()의 원형은 다음과 같다. 

DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds); 

DWORD WaitForMultipleObjects( 
DWORD nCount,             // number of handles in array 
CONST HANDLE *lpHandles,  // object-handle array 
BOOL bWaitAll,            // wait option 
DWORD dwMilliseconds      // time-out interval 
); 


쓰레드 종료를 위한 플래그를 설정한 후, 쓰레드가 완전히 종료된 것을 확인 후에 어떤 작업을 하고 싶으면 다음과 같이 한다. 
if (::WaitForSingleObject(pThread->m_hThread, INFINITE)) 

// 쓰레드가 종료된 후 해야 할 작업들 



(쓰레드 종료를) 어느 정도 기다리다가 프로그램을 진행시키려면 다음과 같이 한다. 
DWORD dwRetCode; 
dwRetCode = ::WaitForSingleObject(pThread->m_hThread, 2000); 
if (dwRetCode == WAIT_OBJECT_0) 

// 쓰레드가 종료된 후 해야 할 작업들 

else if(dwRetCode == WAIT_TIMEOUT) 

// 2초 동안 쓰레드가 종료되지 않았을 때 해야 할 에러 처리 



다음과 같이 하면, 어떤 쓰레드가 현재 실행 중인지 아닌지를 알 수 있다. 
if (::WaitForSingleObject(pThread->m_hThread, 0) == WAIT_TIMEOUT) 

// 현재 쓰레드가 실행 중. 

else 
// 실행 중인 상태가 아니다. 



// WaitForMultipleObjects() sample... 

// 쓰레드 함수의 원형 
DWORD WINAPI increment(LPVOID lParam); 
DWORD WINAPI decrement(LPVOID lParam); 

int main() 

// char* ps[] = {"increment", "decrement"}; 
DWORD threadID; 
HANDLE hThreads[2]; 

// hThreads[0] = CreateThread( NULL, 0, increment, (LPVOID)ps[0], 0, &threadID); 
// hThreads[0] = CreateThread( NULL, 0, increment, NULL, 0, &threadID); 

for (int i=0; i<2; ++i) 

hThreads[i] = CreateThread( NULL, 0, increment, (void *)i, 0, &threadID); 


// 모든 쓰레드가 종료할 때 까지 기다린다. 
// WaitForMultipleObjects(2, hThreads, TRUE, INFINITE); 

int ret; 
ret = WaitForMultipleObjects(2, hThreads, FALSE, INFINITE); 
switch(ret) 

case WAIT_OBJECT_0: // handle hThreads[0] is signaled.. 
break; 
case WAIT_OBJECT_0+1: 
break; 


CloseHandle(hThreads[0]); 
CloseHandle(hThreads[1]); 
return 0; 


DWORD WINAPI increment(LPVOID lParam) 

while (1) 

... 


return 0; 


DWORD WINAPI decrement(LPVOID lParam) 

while (1) 

... 


return 0; 


4) 쓰레드 일시중지 - 재개 

DWORD SuspendThread(HANDLE hThread); - 1 
DWORD ResumeThread(HANDLE hThread); - 2 

둘 다 내부적으로 카운터를 사용하므로 1을 두번 호출했다면 2도 두번 호출해야한다. 그래서 카운터가 0 이되면 쓰레드는 재개하게된다. 


5) 우선순위 조정 

향상된 멀티태스킹을 지원하기 위해서는 시분할 뿐만 아니라 프로세스의 우선순위를 지원해야 한다. 
마찬가지로 프로세스 내부의 쓰레드들도 우선순위를 갖아야 하며 우선순위 클래스, 우선순위 레벨 이 두 가지의 조합으로 구성된다. 

우선순위 클래스는, 스레드를 소유한 프로세스의 우선순위이며 
CreateProcess 함수로 프로세스를 생성할 때 여섯번째 파라미터 dwCreationFlag로 지정한 값이다. 
디폴트는 NORMAL_PRIORITY_CLASSfh 보통 우선순위를 가지므로 dwCreationFlag를 특별히 지정하지 않으면 이 값이 전달된다. 

우선순위 레벨은 프로세스 내에서 쓰레드의 우선순위를 지정하며 일단 쓰레드를 생성한 후 다음 두 함수로 설정하거나 읽을 수 있다. 

BOOL SetThreadPriority(HANDLE hThread, int nPriority); 
Int GetThreadPriority(HANDLE hThread); 

지정 가능한 우선순위 레벨은 총 7가지 중 하나이며 디폴트는 보통 우선순위인 THREAD_PRIORITY_NORMAL 이다. 

우선순위 클래스와 레벨값으로부터 조합된 값을 기반우선순위(Base priority)라고 하며 쓰레드의 우선순위를 지정하는 값이 된다. 
기반우선순위는 0~31 중 하나이며 0은 시스템만 가질 수 있는 가장 낮은 우선순위 이다. (낮을수록 권한이 높음) 

우선순위를 높이는(에이징)방법과 낮추는 방법을 동적 우선순위 라고하며, 우선순위 부스트(Priority Boost)라고 한다. 
단 이 과정은 기반 우선순위 0~15 사이의 쓰레드에만 적용되며 16~31 사이의 쓰레드에는 적용되지 않는다. 
또한 사용자입력을 받거나(인터럽트) 대기상태에서 준비상태가 되는 경우에는 우선순위가 올라가고, 
쓰레드가 할당된 시간을 다 쓸 때마다 우선순위를 내려  결국 다시 기반 우선순위와 같아지게 되는데, 
어떠한 경우라도 동적 우선순위가 기반 우선순위보다는 더 낮아지지 않는다. 

3. 쓰레드간 동기화 

멀티쓰레드는 개요에서 말했듯이 한 프로세스를 여러 역할에 따라 여러 개의 쓰레드로 나뉘어 작업하는 방식이므로 각 쓰레드간의 동기화가 필요하다. 
동시에 복수개의 코드가 같은 주소영역에서 실행됨으로써 서로 간섭하고 영향을 주는 경우가 빈번하기 때문이다. 

멀티쓰레드의 가장 큰 문제점은 공유자원(주로 메모리의 전역변수)을 보호하기가 어렵다는 점이다. 
그리고 쓰레드간의 실행순서를 제어하는 것도 쉽지 않은 문제이다. 

이런 여러가지 문제점을 해결하기 위하여 쓰레드간의 실행 순서를 제어할 수 있는 여러가지 방법들을 동기화라고 한다. 
동기화 방법에는, Interlocked, 임계영역, 뮤텍스, 세마포어, 이벤트등의 기법을 사용한다. 

1) 임계영역 (Critical Section) 

동기화문제를 해결하는 방법들 중 가장 쉬운반면 동일한 프로세스 내에서만 사용해야 하는 제약이 있다. 
임계영역(Critical Section)이란 공유자원의 독점을 보장하는 코드의 영역을 가리킨다. 이는 아래 두 함수로 시작하고 끝낸다. 

VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 
VOID DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 

CRITICAL_SECTION형의 포인터형은 복수개의 쓰레드가 참조해야 하므로 반드시 전역변수로 선언해야한다. 사용법은 다음과 같다. 

CRITICAL_SECTION crit1, crit2; 

함수 { 
… 
EnterCriticalSection(&crit1); 
//공유자원1을 액서스한다. 
LeaveCriticalSection(&crit1); 

EnterCriticalSection(&crit2); 
//공유자원2을 액서스한다. 
LeaveCriticalSection(&crit2); 
… 


주의할것은 가급적 임계영역 내부의 코드가 빨리 끝날 수 있도록 짧은 시간을 사용하도록 작성해야 한다. 
만약 Leave를 호출하지않고 쓰레드를 빠져나와버리면 이후부터는 다른 쓰레드는 이 임계영역에 들어갈 수 없게된다. 
만약 이부분에서 예외가 발생하여 Leave함수가 호출되지 못하게 될 수도 있다. 
그래서 임계영역을 쓸 때는 반드시 구조적 예외 처리구문에 포함시켜주는 것이 좋다. 

Try { 
EnterCriticalSection(&crit); 
… 

finally { 
LeaveCriticalSection(&crit); 


이렇게하면 설사 예외가 발생하더라도 Leave함수는 반드시 호출되므로 훨씬 안전해진다. 

다음은 MFC 에서의 사용 예이다. 

CCriticalSection g_critical; // 전역 변수로 선언 

function() {
    AfxBeginThread(ThreadFuncA, NULL);
    AfxBeginThread(ThreadFuncB, this);
}

UINT ThreadFuncA(LPVOID pParam) {
    while (1) {
        g_critical.Lock();

        // ThreadFuncA가 할 일.... 

        g_critical.Unlock();
    }
    return 0;
}

UINT ThreadFuncB(LPVOID pParam) {
    while (1) {
        g_critical.Lock();

        // ThreadFuncB가 할 일.... 

        g_critical.Unlock();
    }
    return 0;
}



2) 뮤텍스(Mutex) 

임계영역은 앞서 말했듯 동일한 프로세스 내에서만 사용할 수 있다. 
그러나, 뮤텍스(Mutex; Mutual Exclusion;상호배제)는 임계영역이 사용된 곳에 대신 사용될 수 있으며, 프로세스 간에도 사용할 수 있다. 
뮤텍스를 사용하려면 다음 함수로 생성해야 한다. 

HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL blInitialOwner, LPCTSTR lpName); 

lpMutexAttributes : 보안속성. 대개 NULL 
blInitialOwner : 뮤텍스 생성과 동시에 소유할 것인지 지정. 
lpName: 뮤텍스의 이름을 지정하는 문자열. 
뮤텍스는 프로세스간의 동기화에도 사용되므로 이름이 필요하고, 이 이름은 프로세스간 뮤텍스를 공유할 때 사용된다. 

뮤텍스 소유를 해지하여 다른 쓰레드가 이것을 가질 수 있도록 하려면 임계영역의 LeaveCriticalSection 에 해당하는 다음 함수를 호출하면 된다. 

BOOL ReleaseMutex(HANDLE hMutex); 

만일 프로세스가 다른 프로세스의 쓰레드에 의해서 이미 생성된 뮤텍스의 핸들을 얻기를 원하거나, 
뮤텍스가 존재하지 않는 경우에 뮤텍스를 생성하기 원한다면 다음 함수를 사용한다. 

HANDLE OpenMutex(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName); 

3) 세마포어 (Semaphore) 

세마포어도 뮤텍스와 유사한 동기화 객체이나 다른점은, 뮤텍스는 하나의 공유자원을 보호하기 위해 사용하지만, 
세마포어는 제한된 일정 개수를 가지는 자원(HW, 윈도우, 프로세스, 쓰레드, 권한, 상태 등 컴퓨터에서의 모든 자원)을 보호하고 관리한다. 
세마포어는 사용 가능한 자원의 개수를 카운트하는 동기화 객체이다. 
세마포어와 관련된 함수는 다음과 같다. 

HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG IlInitialCount, 
LONG lMaximumCount, LPCTSTR lpName); 

HANDLE OpenSemaphore(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName); 

BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount); 

4) 이벤트 (Event) 

임계영역, 뮤텍스, 세마포어는 주로 공유자원을 보호하기 위해 사용되는 데 비해 
이벤트는 이보다는 스레드간의 작업순서나 시기를 조정하기 위해 사용한다. 
특정한 조건이 만족될 때까지 대기해야 하는 쓰레드가 있을 경우 이 쓰레드의 실행을 이벤트로 제어할 수 있다. 
이벤트는 자동리셋과 수동리셋이 있다. 

+자동 리셋 이벤트 : 대기상태가 종료되면 자동으로 비신호상태가 된다. 
+수동 리셋 이벤트 : 쓰레드가 비신호상태로 만들어줄 때까지 신호상태를 유지한다. 

++신호상태 (Signaled): 쓰레드 실행가능상태. 신호상태의 동기화 객체를 가진 쓰레드는 계속 실행할 수 있다. 

HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, 
BOOL bInitialState, LPCTSTR lpName); 

HANDLE OpenEvent(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName); 

bManualReset은 이벤트가 수동리셋 이벤트(manual)인지 자동리셋 이벤트(automatic)인지 지정하는데 TRUE이면 수동리셋 이벤트가 된다. 
bInitialState가 TRUE이면 이벤트를 생성함과 동시에 신호상태로 만들어 이벤트를 기다리는 쓰레드가 곧바로 실행을 하도록 해준다. 
이벤트도 이름(lpName)을 가지므로 프로세스간의 동기화에 사용될 수 있다. 

또한 이벤트가 임계영역이나 뮤텍스와 다른점은 
대기함수를 사용하지 않고도, 쓰레드에서 임의적으로 신호상태와 비신호상태를 설정할 수 있다는 점이다. 다음 함수를 사용한다. 

BOOL SetEvent(HANDLE hEvent); 
BOOL ResetEvent(HANDLE hEvent); 

SetEvent는 신호상태로 만들고 ResetEvent는 비신호상태로 만든다. 

다음은 MFC 에서의 사용 예이다. 
CEvent g_event; // 전역변수로 선언 

FunctionA() 

AfxBeginThread(ThreadFunc, this); 


FunctionB() 

g_event.SetEvent(); // Lock() 함수에서 더 이상 진행하지 못하고 잠자고 있는 쓰레드를 깨워서 일을 시키려면 SetEvent()를 호출. 


// ThreadFunc() 함수는 이벤트가 발생할 때마다 while문을 한번씩 실행. 
UINT ThreadFunc(LPVOID pParam) 

while(1) 

g_event.Lock();  // SetEvent()가 호출되면, Lock()함수에서 실행이 중단된 쓰레드가 다음 코드를 실행. 

// ThreadFunc가 할 일.... 

g_event.Unlock(); 

return 0; 

 

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://egloos.zum.com/mamma1234/v/9541593

 

1. Process와 Thread

쓰레드라는 놈을 이해하기 위해선 프로세스란 놈을 먼저 이해해야한다. 프로세스는 EXE란 확장명을 가지고 파일을 실행시켰을 때 실질적으로 컴퓨터의 메모리에 자리잡고 앉아서 실행되는 형태를 말한다. 예를 들면 프로그램을 마우스로 더블클릭하면 OS가 그 프로그램을 실행하기 위해, 알맞게 메모리상에 그 프로세스가 실행가능한 영역을 잡아주고(이런 영역에는 여러가지 영역이 있다.코드부 or 스택부), 프로그램은 거기에 들어앉아서 온갖 사용자의 명령을 다 받으며 자기의 일을 수행한다.

 

즉, 프로세스는 메모리에 적재된 상태로 동작하는 프로그램이다.

 

프로세스를 이루는 기본단위가 쓰레드라 볼 수 있다. 프로세스는 적게는 수십개, 많게는 수백개의 Thread로 이루어져 있다. 즉 이런 쓰레드들이 실처럼 일률적인 순서대로 늘어져 원하는 작업을 수행하고 있는 것이죠. 쉽게 보면 쓰레드 하나가 특정한 작업을 하나 수행한다고 생각 할 수 있다.

 

프로그램을 짜다보면 여러가지 상황이 발생하는데, 블록킹모드, 논블록킹 모드, 동기모드, 비동기모드 등등.. 예를 하나 들어 보면,

 

while( true )

 

{

 

int count;

 

printf( “Current number %d”, count );

 

count++;

 

}

 

위의 루틴에 들어가게 되면 프로그램은 블록(무한 Loop에 빠짐)된다. 사용자는 어떤 입력도 프로그램에 가 할 수 없다. 그래서 사람들은 저런 반복적으로 작업하는 계산 루틴 등을 쓰레드로 만들어 버린다. 즉 계산과정을 쓰레드로 만들어서 하나의 작은 작업으로 처리해 버리는 것이다. 그러므로 저렇게 반복적인 작업이나 시간이 오래 걸리는 작업, 또는 다른 작업과 병행해서 일을 해야 하는 경우는 쓰레드를 사용하게 된다.

물론 처리 속도 문제는 조금 다른 문제이다. 단지 쓰레드로 프로그램을 작성하면, OS차원에서 쓰레드가 CPU를 사용할 시간을 분배(Schedule)하는 것뿐이다. 단지 이 시간의 간격이 무지 짧기 때문에 사용자는 느끼지 못하고, 마치 순간적으로 동시에 작동하는 것처럼 보인다.

쓰레드는 사용자에게 융통성 있는 사용환경을 만들어 주기 위해서 개발자가 충분히 검토하고 개발에 적용해야할 테크닉이다.

 



2. 쓰레드의 장점

 

A. 애플리케이션의 일부가 비동기적이고 병렬적으로 실행가능하게 함

 

B. 코드의 복사본을 여러 개 수행하여 여러 개의 클라이언트에게 동일한 서비스 제공가능

 

C. 블록(죽어버릴 수 있는)될 가능성이 있는 작업을 수행할 때 방지할 수 있음

 

D. 멀티프로세서 시스템을 효과적으로 사용 할 수 있음

 



A. 애플리케이션의 일부가 비동기적이고 병렬적으로 실행가능하게 함

 

동기적이란 어떤 작업이 순차적으로 실행되어야 함을 말한다. 예를 들어 어떤 함수 두개가 있다고 가장하자.

 



int a = 2, b = 3, c;

 

int d = 4, e = 5, f;

 

funcA { c = a+b; }

 

funcB { f = d+e; }

 

단일 쓰레드 환경에서는 함수A를 호출한 다음에 함수B를 호출한다.

 

funcA()

 

funcB()

 

그런데 함수A와 함수B가 서로 완전히 다른 데이터를 가지고 작업을 한다면, 함수가 실행되는 순서에는 상관이 없다.

 

즉 함수의 호출 순서가 바뀌어도 상관이 없다.

 

funcB()

 

funcA()

 



그러나 새로운 함수 C를 만들어 보자.

 

in funcC { return c + f; }

 

위의 함수는 함수A와 함수B의 결과값을 가지고 연산을 하기 때문에, 함수A와 함수B가 완전히 실행이 끝난 다음에야 호출할 수가 있다. 위와 같이 순차적으로 실행을 해야만 하는 순차적인 프로그램 방식을 동기화(Synchronization)라고 한다. 비동기화는 위와 같이 순차적으로 실행을 안 해도 되는 것이다.

 

단일 쓰레드를 지원하는 OS에서는 반드시 위와 같은 절차를 가지고 프로그래밍을 해야 하지만 멀티 쓰레드를 사용하는 OS에서는 프로그램을 작성할 때 각 부분 사이에 상호관계를 반영하여 설계를 할 수 있도록 하는 것이다.

 



B. 코드의 복사본을 여러 개 수행하여 여러 개의 클라이언트에게 동일한 서비스 제공가능

 

Network 프로그램 같은 경우 사용자의 접속을 계속 처리하기 위해서 쓰레드를 사용한다. 즉 사용자가 접속 요청을 하면 하나의 쓰레드와 연결시켜준다. 여러 사용자가 단일 쓰레드의 서버 프로그램에 접속해서 서비스를 받는다고 가정하면, 한 명이 접속해서 그 사람이 접속을 끊을 때까지 다른 사용자는 서비스를 받을 수 없다.

 



C. 블록(죽어버릴 수 있는)될 가능성이 있는 작업을 수행할 때 방지할 수 있음

 

Network 프로그래밍에서 블록 되는 코드가 있다고 가정하자. 대표적으로 보면 accept()가 있는데, 이 함수는 사용자가 접속요청을 할 때까지 멈춰 있는다. 사용자 접속요청이 없으면 무한정 기다리게 된다. 그래서 접속요청을 받는 부분을 쓰레드로 만들어서 프로그램이 블록되지 않게 하는 것이다. 또 다른 예로, 탐색기에서 덩치가 큰 파일을 복사를 걸어놓고도 곧바로 탐색기를 다시 쓸 수 있는데, 이는 복사하는 루틴을 쓰레드로 만들었기 때문이다.

 



D. 멀티 프로세서 시스템을 효과적으로 사용 할 수 있음

 

CPU가 많은 시스템에서 쓰레드를 돌리면 좀더 효율적이다.

 



3. 멀티쓰레드 애플리케이션 구조

 

멀티쓰레드를 이용한 애플리케이션을 작성하는 구조에는 3가지 방법이 있다.

 

A. boss/worker모델.

 

B. work crew모델.

 

C. 파이프라이닝 모델.

 



A. Boss/Worker Model

 

쓰레드(주쓰레드)가 필요에 따라 작업자 쓰레드를 만들어 내는 경우에, 위에서 예를 든 것과 같이 접속받는 부분을 주쓰레드(boss)로 돌리고, 접속요청이 오면 새로운 쓰레드(worker)를 만들어 사용자와 연결시켜 주는 방법입니다.

 



B. Work Crew Model

 

어떤 한 작업을 여러 개의 쓰레드가 나눠서 하는 방식이다. 예를 들어, 집을 청소하는 작업에 대한 쓰레드를 여러 개 돌려, 방바닥 닦는 사람, 먼지 터는 사람 이런 식이다. 특정한 청소를 하는 작업이 쓰레드 하나가 되는 것이다.

 



C. Pipe Lining Model

 

이 구조에서는 순서대로 실행되어야할 작업이 여러 개가 있을 경우, 작업 1은 작업 2에게 전달하고, 작업2는 작업3에게 전달하는 방식으로 순차적으로 진행되어야 하지만 최종결과를 여러 개 만들어내기 위해서 동시에 수행될 필요가 있을 경우 멀티쓰레딩을 사용한다. 예를 들면 공장의 라인에서는 서로 다른 작업들을 수행하지만, 결국 하나의 결과물을 만들어 내기 위한 과정이다.

 



4. 쓰레드의 생성

 

쓰레드는 UI( User Interface ) Thread와 Worker( 작업자 ) 쓰레드로 나뉜다.

 

UI Thread는 사용자 메시지 루프를 가지고 있는 쓰레드로서, 즉 어떤 메시지가 날라오면 일하는 쓰레드를 말한다.

 

Worker 쓰레드는 보통 오래 걸리는 작업이나 무한루프를 가지는 작업을 하는 사용자 정의 함수를 위한 것이다.

 



윈도우에서 쓰레드를 생성하는 방법은 여러가지가 있다. 즉 윈도우라는 OS에서 제공해주는 라이브러리함수를 가지고 쓰레드를 만드는 것이다.

 



Wokrer 스레드를 만드는 방법을 알아보자.

 

어떤 프로그램이 Dialog 기반으로 돌아가며, 화면에 2초에 한번씩 숫자를 더하면 화면에 바뀐 숫자를 표시한다고 가정한다.

 



① 화면에 무한적으로 숫자를 뿌리는 함수를 만듦

 

UINT ThreadFunc( void* pParam )

 

{

 

int count;

 

while(true)

 

{

 

count++;

 

GetDlgItem( IDC_STATIC )->SetWindowText( atoi(count) );

 

Sleep( 2000 );

 

}

 

}

 

//정확한 코드는 아님

 

// IDC_STATIC는 static_control 의 resource id 임

 



이 함수는 2초에 한번씩 count를 증가시키면서 Dialog box에 증가된 값을 화면에 뿌린다. 이 함수는 쓰레드로 돌리려 한다.

 



② Worker 쓰레드 생성하기

 

작업자 쓰레드로 특정한 작업을 하는 사용자 정의 함수를 만들기 위해서, 윈도우에서는 여러가지 쓰레드 생성 함수를 제공해 준다.

 

1. CreateThread()

 

2. _beginthread(), _beginthreadex()

 

3. AfxBeginThread(), AfxBeginThreadEx()

 



1. CreateThread()

 

win32함수로써, 중요한 parameter만 살펴본다.

 

HANDLE handle;

 

Handle = CreateThread( Threadfunc(), Param );

 

첫번째 parameter는 위에서와 같이 사용자가 쓰레드로 돌려야할 작업함수를 써 주며, 두 번째 parameter는 작업함수에 parameter값으로 전달할 값이다. 이 parameter는 VOID*으로 되어 있기 때문에 4BYTE이내의 값은 어떤 값이든 들어갈수 있다. 대신 TYPE CASTING을 해주어야 합니다.(ex. (int) 3). 이 함수가 올바르게 실행이 되면 쓰레드에 대한 핸들을 반환하는데, 이 핸들을 가지고 쓰레드를 조작할 수 있게 된다. 대표적으로 쓰레드를 닫을 때 CloseHandle()함수를 사용해서 쓰레드 핸들을 넣어주고 쓰레드를 닫아 준다. 그러나 이 함수로 생성된 쓰레드를 닫을때는 ExitThread()면 된다.

 



2. _beginthread(), _beginthreadex()

 

CreateThread는 쓰레드에서 win32 API함수만 호출할 수 있다. 즉, 사용자가 어떤 작업을 하는 함수를 만들 때 CreateThread로 만들게 되면, 그 함수안에서는 win32 API만 사용할 수 있고, C함수나 MFC는 못쓴다. 그러나 _beginthread() 함수는 win32 API와 C 런타임 함수를 사용할 때 사용한다. 주의할 점은 이 함수도 쓰레드 핸들을 리턴하는데, 이 핸들을 가지고 쓰레드에 조작을 가 할 수 있다. 대신 이 함수를 사용하면 C 런타임 라이브러리가 핸들을 자동으로 닫으므로 이를 직접 닫을 필요는 없다. 하지만, _beginthreadex는 스레드 핸들을 직접 닫아야 합니다. 그리고 이 쓰레드를 닫을 때는 _endthread(), _endthreadex()를 사용하면 된다.

 



3. AfxBeginThread(), AfxBeginThreadEx()

 

실질적으로 가장 자주 사용하는 쓰레드 생성함수 이다. 이 함수를 이용하면 사용자 정의 함수내에서 MFC, win32 API, C 런타임 라이브러리등 여러가지 라이브러리 함수들을 전부 사용할 수 있다. 이 함수는 리턴값이 CwinThread* 형을 리턴하는데, 이 객체를 사용하면, 생성된 쓰레드에 여러가지 조작을 가할 수 있다. AfxEndThread()를 사용해서 종료 시킬 수 있다. 쓰레드가 종료되면 MFC는 쓰레드 핸들을 닫고 리턴값으로 받은 CwinThread*객체를 제거한다.

 



5. MFC에서 작업자 쓰레드 생성하기

 

MFC에서 쓰레드를 만드는 방법은 두 가지가 있다. CwinThread의 멤버 함수인 CreateThread를 사용하는 방법과 AfxBeginThread()를 사용하는 방법이다. 후자는 CwinThread객체를 만들 수 있다.

 

또한 MFC에서는 AfxBeginThread의 서로 다른 버전 두개를 정의 하고 있다. 하나는 작업자 쓰레드를 위한 것이고, 하나는 UI쓰레드를 위한 것인데, 이 두 가지 정의대한 소스코드는 Thrdcore.cpp에서 볼 수 있다.

 



CwinThread* pThread = AfxBeginThread( Threadfunc, &threadinfo );

 



▪ nProiority는 쓰레드의 우선순위를 지정한다. 기본적으로 THREAD_PRIORITY_NORMAL이 들어가는데 이는 평범한 순서로 작동시키겠다는 말이다. 이 우선순위는 나중에 리턴값으로 받은 CwinThread* 의 멤버 함수인 SetThreadPriority를 가지고 변경해 줄 수 있다.

 

▪ dwCreateFlags는 0 아니면 CREATE_SUSPEND이다. 기본값은 0이 들어가는데 0이면 바로 쓰레드가 실행되는 것이고 두 번째이면 쓰레드는 정지된 상태로 만들어지고 리턴값으로 받은 객체의 멤버함수인 ResumeThread를 호출하면 쓰레드를 시작한다.

 

▪ lpSecurityAttrs는 보안속성이다.

 



AfxBeginThread() 함수의 첫번째 인자로 들어갈 작업함수의 원형은 다음과 같다.

 

UINT ThreadFunc ( LPVOID pParam )

 



이 함수는 static 클래스 멤버 함수 이거나 클래스 외부에서 선언한 함수여야 한다.

 

즉, 어떤 함수를 클래스 멤버함수로 선언해서 쓰레드로 사용하려면 클래스내부에 함수 프로토타입 선언시 static로 선언해 주어야 한다는 건데, static 멤버 함수는 static 멤버 변수에만 접근 할 수 있기 때문에 제약이 많다.

 



인자값은 AfxBeginThread에서 두번째 인자로 들어갔던 32bit값의 파라미터 인데, 일반적으로 그냥 데이터형을 넘겨줄 수도 있고, 많은 데이터가 있을 때에는 구조체를 만들어서 포인터로 넘겨주어서 함수내부에서 다시 풀어서 사용한다.

 



쓰레드 함수 내부에서 만들어진 객체나 변수들은 쓰레드 자신의 내부 스택에 생성되기 때문에 다른 쓰레드에서 데이터를 변경하거나 하는 그런 문제들은 없다.

 



6. 쓰레드 멈추고 다시 실행하기

 

AfxBeginThread() 함수로 작업을 할 함수를 스레드로 돌리고 그 스레드가 올바르게 작동이 되면 CwinThread*형을 리턴값으로 받는다는 것을 알게 되었다. 이렇게 되면 우리는 이 실행시킨 스레드를 사용자가 멈추고 싶을 때 멈추게 할 수 있고, 또 멈췄으면 언젠가 다시 실행을 시켜야 한다.

 

우선 실행중인 스레드함수를 멈추게 할때는 우리가 리턴값으로 돌려받은 CwinThread*의 변수를 가지고 작업을 할 수 있다.

 

CwinThread::SuspendThread()

 

위의 함수가 바로 실행중인 스레드를 멈출 수 있게 하는 함수이다. 이 함수는 현재 실행중인 스레드 내부에서 호출할 수도 있고, 또한 다른 쓰레드 내에서도 호출할 수가 있다.

 

CwinThread::ResumeThread()

 

위 함수는 중지된 쓰레드를 깨우는 함수이다. 이 함수는 위의 SuspendThread같이 스레드 자기자신의 내부에서는 호출할 수가 없다. 다른 쓰레드나 메시지 Handler 함수들을 이용해서 ResumeThread()를 호출해야지 멈춰진 쓰레드를 다시 실행 할 수 있다.

 



(참고) 스레드 내부로 복잡하게 들어가면 내부적으로 각 스레드 함수는 Suspend count란 것을 유지하고 있어서 이 값이 0일 때만 스레드가 CPU시간을 할당받아 실행이 된다. 즉 AfxBeginThread함수에 dwCreateFlags에 CREATE_SUSPEND를 넣어줘서 실행한다거나 CwinThread::SuspendThread()를 실행하거나 하면 Suspend count는 1이 증가가 된다. 그래서 스레드는 멈추게 되고 다시 CPU로부터 시간을 할당받아 스레드가 활동을 재개하게 하려면 ResumeThred()를 호출해서 suspend count를 0으로 만들어 주어 CPU시간을 할당받게 해서 스레드를 다시 시작한다. 내부적으로 이런 메커니즘을 가지고 스레드는 동작하기 때문에 SuspendThread()를 두 번 호출하면 suspend count가 2가 되고, RecumeThread()도 두 번 호출해 줘야 스레드가 다시 활동할 수가 있다.

 

7. 스레드 잠재우기

 

쓰레드를 잠재우는 이유는 크게 두 가지가 있다.

 

하나는 에니메이션을 구현하거나 아날로그 시계의 바늘 같이 시간이 경과함에 따라 화면에 그림을 그린다든지 하는 경우, 즉 시간 경과에 따라 실행하여야 하는 프로그램의 경우이고, 두 번째는 어떤 스레드가 현재 실행되어야 하는 시점에서 다른 쓰레드에게 양보를 할 경우에 사용된다.

 

첫 번째의 경우는, 한마디로 쓰레드 내부에서만 사용하는 쓰레드 타이머라고 생각할 수 있다. 즉 일정시간이 지나면 반복적으로 작업을 할 때 유용하다.

 



쓰레드 내부에서도 Sleep()라는 걸 이용하면 쓰레드 내부 루틴을 타이머를 사용했을 때와 비슷하게 만들 수 있다.

 

쓰레드를 잠재울 때는 다음의 함수를 사용한다.

 

::Sleep( 0 );

 

위 함수는 win32 API함수이다. 따라서 사용할 때 :: 를 이용해서 호출한다. 만약 위의 함수를

 

::Sleep( 5000 );

 

하면 이 쓰레드는 5초 동안 CPU시간을 쓰지 않게 되며, 이렇게 잠들어 있을 때는 다른 쓰레드가 CPU시간을 얻어서 활동하게 된다.

 



두번째는 위의 첫 번째 예와 같이 인자로 0을 주는 경우인데, 이 경우에 현재 스레드를 정지하고(0 이면 0.001초인데..멈추다니..) 이 스레드가 CPU시간을 풀어줄 때까지 기다리던 다른 스레드가 실행된다. 그런데 다른 스레드는 이 스레드와 우선순위가 같아야 한다. 그리고 같은 우선순위의 스레드가 없다면 현재 sleep(0)을 실행했던 스레드가 계속 실행이 된다.

 

스레드 안에서 sleep()를 쓰는 경우는 시간의 지연을 두고 작업을 해야 하는 경우에 많이 사용된다. (ex. 아날로그 시계)

 



CwinThread* pThread = AfxBeginThread( TimeFunc, pParam );

 



UINT TimeFunc( void* pParam )

 

{

 

void* param = (void*) pParam; // 사용자가 넘겨준 데이터를 사용하기 위함

 

while( true )

 

{

 

::Sleep( 1000 ); // 이곳에서 1초가 멈춰진다.

 

DrawTime(); // 초침을 그리는 함수

 

}

 

}

 



7. 스레드 죽이기

 

쓰레드를 죽이는 이유는 자기의 일을 다 마치고 나면 당연히 쓰레드는 없어져야 하기 때문이다. 그렇지 않으면, 쓰레드는 여전히 자기가 들어앉은 메모리 속에 앉아서 컴퓨터 리소스만 잡아먹고 있게 된다.

 

스레드를 죽이는 방법엔 두 가지가 있다.

 

① 스레드 내부에서 return을 시킬 때.

 

② AfxEndThread를 호출할 때.

 



안전한 방법은 스레드 내부 자체에서 return문을 이용하는 것이 안전하다. 탐색기도 이와 같은 방법을 사용한다. 복사가 다 끝나면 자동적으로 return을 시키기 때문에 스레드가 알아서 죽는다. 이런 방법은 쓰레드가 오랜 시간을 작업하는경우에 사용한다. 즉 언젠가는 결과를 얻어서 끝 날수 있는 일을 할 때 사용하게 되고, 대개 무한루프를 돌면서 하는 일은 AfxEndThread를 사용해서 끝낸다.

 



위의 첫 번째 방법과 같이 return을 받았을때는 GetExitCode를 이용해서 검색할 수 있는 32bit의 종료 코드를 볼수 있다.

 



DWORD dwexitcode;

 

::GetExitCodeThread( pThread->m_hThread, &dwExitCode );

 

// pThread는 CwinThread* 객체의 변수이고..

 

// m_hThread 는 CwinThread내부의 생성된 쓰레드 핸들

 



이 문장은 실행이 종료된 스레드의 상태를 보여주는 문장이다. dwExitCode에 스레드가 return 하면서 나온 32bit의 종료값이 들어가게 된다. 만약 실행중인 스레드를 대상으로 저 코드를 쓰게 된다면 dwExitCode에는 STILL_ACTIVE라는 값이 들어가게 된다.

 

그런데, 위의 코드를 사용함에 있어 제약이 있다. CwinThread*객체는 스레드가 return 되어서 스스로 종료가 되면 CwinThread 객체 자신도 혼자 제거되어 버린다. 따라서 delete시켜주지 않아도 메모리에서 알아서 없어진다.

 

return이 되어서 이미 죽어버린 스레드를 가지고 pThread->m_hThread를 넣어주면, 이것은 이미 return되어 죽어버린 스레드의 핸들을 가리키고, Access위반이란 error메시지가 나오게 된다.

 

이런 문제를 해결하려면 CwinThread* 객체를 얻은 다음 이 객체의 멤버 변수인 m_hAutoDelete를 FALSE로 설정하면 스레드가 return을 해도 CwinThread객체는 자동으로 제거 되지 않기 때문에 위의 코드는 정상적을 수행된다.

 



이런 경우에 CwinThread*가 더 이상 필요가 없어지면 개발자 스스로 CwinThread를 delete시켜 주어야 한다. 또 다른 방법으로 스레드가 가동이 되면 CwinThread*의 멤버변수인 m_hThread를 다른 곳으로 저장을 해놓고 이 것을 직접GetExitCode()에 전달을 하면 그 쓰레드가 실행중인지 한때는 실행되고 있었지만 죽어버린 스레드인지 확인이 가능하다.

 



int a = 100; // 파라미터로 넘겨줄 전역변수.

 

CwinThread* pThread // 전역 쓰레드 객체의 포인터 변수.

 

HANDLE threadhandle; // 스레드의 핸들을 저장할 핸들변수.

 



Initinstance() // 프로그램초기화.

 

{

 

// 프로그램 실행과 동시에 스레드 시작.

 

1번방법:pThread = AfxBeginThread( func, (int) a );

 

// 스레드가 리턴되면 자동으로 CwinThread객체가 자동으로 파괴되지 않게 설정.

 

2번방법:pThread->m_hAutoDelete = FALSE;

 

// 쓰레드 핸드를 저장. 위의 m_hAutoDelete를 설정하지않았을경우..

 

threadhandle = pThread->m_hThread;

 

}

 



MessageFunction() // 어떤 버튼을 눌러서 스레드의 상태를 알고 싶다..

 

{

 

char* temp;

 

DWORD dwExitcode;

 

// 스레드 객체의 m_hAutoDelete를 fasle로 설정해서 스레드가 return되어도

 

// 객체가 자동으로 파괴되지 않아서 핸들을 참조 할수 있다.

 

1번방법: ::GetExitCode( pThread->m_hThread, &dwExitcode);

 

// 스레드가 종료되고 미리 저장해둔 핸들을 이용할경우..

 

2번방법: ::GetExitCode(threadhandle, &dwExitcode);

 

sprintf( temp, “Error code : %d”, dwExitcode );

 

// 스레드 객체 삭제..

 

1번방법: delete pThread;

 

AfxMessageBox( temp );

 

}

 



func( void* pParam )

 

{

 

int b = (int) pParam;

 

for( int I = 0; I < b; I++)

 

{

 

// 어떤일을 한다.

 

}

 

return; // 작업이 끝나면 리턴한다. 이때 스레드 자동으로 종료.

 

}

 



위의 코드는 어떠한 작업을 수행하는 쓰레드를 돌리고 작업이 끝나면 자연적으로 리턴을 시키는 가상 코드이다.

 



1번째 방법은 스레드를 생성하고 m_hAutoDelete를 false로 해서 스레드가 return해서 자동종료해도 CwinThread를 자동파괴하지 않게 하고, GetExitCodeThread()를 호출한다. 밑에서 delete 는 꼭 해야한다.

 



2번째는 m_hThread를 다른 핸들변수에 저장해 놓고..스레드가 return되면 CwinThread*도 같이 파괴가 된다. 원래 저장한 핸들을 가지고 GetExitcodeThread()를 호출해서 한때 존재했지만 종료된 쓰레드를 검사하는 것이다.

 



8. 스레드 조작

 

하나의 스레드에서 다른 스레드 종료시키기..

 



① 두개의 스레드가 동작하고 있을 때 하나의 스레드에서 다른 스레드를 일방적으로 종료시키는 방법을 알아본다.

 

*** 스레드 A ***

 

static BOOL bContinue = TRUE;

 

CwinThread* pThread = AfxBeginThread( func(), &bContinue )

 

//.........

 

bContinue = FALSE;

 



*** 스레드 B ****

 

UINT func( LPVOID pParam )

 

{

 

BOOL* pContinue = (BOOL*)pParam; //스레드 A에서 넘겨준 종료플래그

 

While( *pContinue )

 

{

 

//..........

 

}

 

return 0; // 끝나면 스레드 B 자동종료.

 

}

 



일반적으로 스레드 사이에서 서로를 사살할 때 많이 쓰는 방법이다. 이 방법은 보통 BOOL형식의 플래그를 하나 둠으로써 그 값을 어떤 스레드에서 변형하여 다른 스레드에서 그 값의 변화를 인지하게 함으로써 스레드를 자동종료( return )시켜버리는 것이다. 보통 이것은 스레드들이 통신하기에 적합하지 않은 방식이라고 많은 서적에서 설명하고 있다. 보통은 스레드들의 동기화 객체를 이용하는 적이 효과적인 방법이다.

 



우선은 스레드 A에서 static 형의 정적인 BOOL형 변수를 하나 만든다. Static으로 선언된 변수는 프로그램이 생성됨과 동시에 자동으로 메모리에 올라가 있게 된다. 그래서 그 변수가 선언된 블록을 빠져 나오게 되더라도 계속적으로 그 값을 유지하고 있다. 그래서 static이라고 플래그 변수를 선언하고 TRUE로 초기화 시켜 둔다.

 



그러면 스레드 B는 BOOL형 값의 플래그를 판단하여 실행여부를 결정하는 while루프안에다가 스레드에서 수행해야 할 일을 넣어주고, 스레드 A에서 더 이상 B가 존재할 이유가 없다고 생각하면 플래그의 값을 FALSE 로 변경시켜서 스레드 B를 자연스럽게 죽게한다.

 

그런데 이 방법은 대부분 사용되지 않는다.(비효율적이므로) 왜냐하면, 스레드 A에서 bContinue = FASLE로 설정한 다음 어떤 작업을 다시 들어가는데, 만약 CPU에서 스레드 스케줄링을 잘못하여 스레드 B가 종료되기 전에 스레드 A에서 또 다른 어떤 작업을 들어가야 한다면, 원하지 않았던 결과를 초래한다.

 



② 그래서 우리는 이때 스레드 A에서 스레드 B가 죽을 때까지 기다리는 매커니즘(방법)이 필요하다.

 



// 스레드 A

 

static BOOL bContinue = TRUE;

 

CwinThrad* pThread = AfxWinThread( func(), &bContinue );

 

// 어떤일 수행.

 

// 생성된 스레드의 핸들을 HANDLE변수에 저장.

 

HANDLE hthread = pThread‐>m_hThread; //m_hThread는 생성된 스레드의 핸들

 



BContinue = FALSE;

 

::WaitForSingleObject( hthread, INFINITE ); //졸라리 중요한 함수임다

 



// 스레드 B

 

UINT func( LPVOID pParam )

 

{

 

BOOL* pContinue = (BOOL*) pParam;

 

While( *pContinue )

 

{

 

// 어떤일 수행.

 

}

 

return 0;

 

}

 



WaitForSingleObject란 Win32함수의 프로토타입은 다음과 같다.

 

DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilisecond );

 

첫번째 인자로 주어진 핸들에 위의 예문에서 실행시킨 스레드의 핸들값을 저장한다. 그리고 두번째 인자로 시간값을 받는데, 이 값은 어떤 행동이 일어나기까지 기다릴 시간을 말한다. 이 함수의 목적은 첫번째 핸들값으로 주어진 그 핸들에 대한 해당하는 어떤객체(?)가 “신호를 받은 상태” 가 될 때까지 기다린다. 예제를 보면 스레드A가 스레드B를 종료시키려고 종료플래그를 FALSE로 셋팅하고 waitforsingleobject()를 호출하고 있는데, 이때 신호를 받아야하는 객체로 스레드B의 핸들을 지정하는 것이다. 이 말은 스레드 B가 정상적이든, 혹은 종료신호가 올 때까지 기다린다는 의미이다.

 

네트웍 프로그램에서 어떤 파일을 받는 스레드가 있고, 이 받은 데이터로 먼가를 작업하는 스레드 2개가 존재한다면 모든 데이터가 다 받아 질 때까지 받은 데이터로 작업하는 스레드는 멈춰 있어야한다. 이 경우 저 함수를 이용해서 받은 데이터로 작업을 수행하는 스레드를 멈춰 둠으로써 안정적으로 작동하게 하는 것이다.

 



이때 WaitForSingleObject에 두번째 인자에 INFINITE를 넣지 않고 5000이라는 값을 넣으면 무한정 기다리는 대신 그 스레드가 신호를 받기를 기다리는 시간을 5초를 지정한 겁니다. 여기서 5초가 경과된 후에도 이 객체가 신호를 받지 못하면, WaitForSingleObject는 반환이 되는데 이 반환된 값을 가지고도 프로그래머가 스레드의 상황을 파악할 수 있다.

 

WAIT_OBJECT_0 : 객체가 신호를 받았음

 

WAIT_TIMEOUT : 객체가 신호를 받지 못했음

 



//스레드 A(받은 데이터를 가지고 어떤 작업을 한다..)

 

………

 

if(::WaitForSingleObjcet(hThread, 5000) == WAIT_TIMEOUT )

 

{

 

AfxMesageBox(“5초가 지나도록 데이터 못 받았다..에러다..”)

 

}

 

else

 

{

 

//데이터 다 받았으니까..알아서..해라..

 

}

 



//스레드 B(데이터를 받는다..)

 

…작업중…

 



다른 방법으로는 WaitForSingleObjct의 두번째 인자에 0을 지정함으로써 이 스레드 함수가 실행중인지 혹은 종료가 되었는지도 알 수 있다.

 



if( ::WaitForSingleObjcet( hThread, 0 ) == WAIT_OBJCET_0 )

 

{

 

// 스레드가 종료가 됬당..

}

 

else

 

// 스레드가 아직 실행중이다..

 

즉 두 번째 인자에 0이란 값을 주면, 그 스레드에 대한 상태를 바로 반환해 주게 된다.

 



CwinThread*의 멤버변수중에 하나인 m_bAutoDelete를 false로 설정해 놓지도 않고, CwinThread가 신호를 받았는지 알기위해서 WaitforSingObject를 호출하는 것은 말이 안됩니다. 이유는 그 스레드가 끝이나면 해당 CwinThread*객체 또한 자동으로 사라지기 때문에, m_bAutoDelete를 false로 해주지 않으면, 말이 안된다.

 

③ 마지막 방법은 ::TerminateThread( pThread->m_hThread , 0 ); 이다.

 

이것은 어떤 스레드에서 다른 스레드를 종료시킬 때 최후의 수단으로 쓸 때 요긴하다. 위의 함수를 사용하면 pThread->m_hThread를 종료하고, 그 스레드에 종료코드 0을 할당한다. 이 함수는 사용함에 있어 제약이 많이 따른다.

 

예를 들어 동기화 객체를 예로 들수 있는데, 어떤 동기화 객체를 공유하는 스레드들이 있는데.. 어떤 스레드가 공유하는 동기화 객체를 Locking하고 작업을 수행하던중,(이때 다른 스레드들은 공유 동기화 객체를 얻기 위해 대기상태에 있다) 위의 TerminateThread함수를 사용해서 작업중인 스레드를 죽이면, 공유된 동기화 객체를 UnLocking하기 전에 죽어 버릴 수 있게 된다. 이러면 공유된 동기화 객체는 영원히 Locking되어 있기 때문에 그 동기화 객체를 공유하는 다른 스레드들은 실행이 안 되게 된다.

 

----------------

 

[MFC] 다음은, CWinThread를 override해서 UI스레드를 만들때의 주의사항이다.

 

1. CWinThread::InitInstance를 반드시 오버라이드 해야하고, 이 메소드에서 TRUE를 반환해야 한다. TRUE를 반환하지 않은 경우, Thread가 생성되자 마자 곧 파괴되어 버린다. (AfxThreadEntry 함수 참조)

 

2. m_bAutoDelete 멤버를 주의해서 사용해야 한다. 보통 이 멤버를 FALSE로 놓고 쓰는 경우를 많이 보아 왔는데, 자칫하면 메모리가 샐수 있다. 이것은 Worker스레드에도 해당되는 내용이다.

 

3. 스레드를 무조건 종료시키지 말고, 스레드 내부에 처리해야 할 메시지가 아직도 남아 있는지 확인한 다음 종료시키도록 한다. 특히 하드웨어 디바이스와 관련 있는 스레드들은 관련된 메시지를 완전히 처리하지 않은 채로 스레드를 강제 종료 시키려 하는 경우, 프로세스 자체가 죽어버린다든지 하는 경우를 보았다.

 

4. CWinThread 하위 클래스에서 MFC를 사용하는 것은 안전하지만, 일반 스레드(CreateThread API를 사용하거나, _beginthreadex를 사용하거나 해서 만든 스레드들)에서 MFC를 사용하는 것은 안전하지 않기 때문에 주의해야 한다. 이것은 Worker스레드에도 해당되는 내용이다.

 

5. 서로 다른 스레드에서 소유하고 있는 CWnd 객체를 넘겨주지 않도록 한다. MFC는 TLS(Thread Local Storage)를 이용해 핸들 맵을 구현하였으므로, 서로다른 스레드간에는 핸들 맵이 틀리다. 직접 CWnd 개체를 다른 스레드에 넘겨주게 되면, 그 스레드에는 존재하지 않는 윈도우 핸들일 수 있으므로 오작동하게 된다. 이것은 Worker스레드에도 해당되는 내용이다.

 

6. 서로 다른 스레드에 윈도우를 전달해야 할 때는 핸들을 이용하도록 한다. CWnd::GetSafeHwnd() 멤버를 이용해 다른 스레드로 윈도우 핸들을 전달하고, 전달 받은 스레드에서 CWnd 멤버에 접근해야 할 필요가 있을경우, CWnd::FromHandle 등의 static 함수를 이용해 임시 CWnd 개체를 만든다음 접근한다.

 

7. 4, 5의 이유때문에 다른 스레드에 존재하는 메인 윈도우를 얻기 위해 AfxGetMainWnd()를 호출하는 것은 안전하지 않다. MFC 7.1에서는 메인 스레드가 아닌 다른 스레드에서 아예 접근 못하게 막아 버렸다. 이것은 Worker스레드에도 해당되는 내용이다.

 

 

 

=======================

=======================

=======================

 

 

 

 

출처ㅣ http://blog.naver.com/PostView.nhn?blogId=mycpp&logNo=120100976531

 

 

 MFC에서 뷰클레스 기반으로 프로그램을 작성할때, 스레드에서 화면을 처리하는 작업을 하려면 어떻게 해야할까...?

 

스레드 함수는 반듯이 전역 함수로 선언 되어야 하기 때문에, 바로 뷰클레스 DC객체를 쓸 수 없다.

 

때문에 현제 런타임중인 뷰클레스 객체를 넘겨주기 위해서는 몇가지 작업을 해 줘야 한다.

 

방법은 윈도우 핸들을 넘겨주고 거기서 뷰클레스를 얻어내야 한다.

 

그후에는 CDC객체로 그리면 되는거다.

 

struct ThreadArg
{
    HWND hwnd;
};

 

스레드로 넘겨줄 구조체를 이렇게 만들었다.

 

그리고 뷰클레스에서 헤더 파일에 구조체 변수 하나만든다.

 

ThreadArg arg1; // 이렇게

 

그리고 스레드를 돌려야 하는 타이밍에, 윈도우 핸들을 줘야 한다.

 

 arg1.hwnd = this->m_hWnd;
 pThread1 = AfxBeginThread(MyDraw, &arg1, THREAD_PRIORITY_NORMAL, 0, 0);

 

이렇게...

 

위에 this->m_hWnd, 이건

 

상위 클레스인 C윈도우 클래스의 멤버 변수인데, 이렇게해서 HWND를 넘겨준다

 

스레드에서는 어떻게 쓰나 보자


UINT MyDraw(LPVOID arg)
{
 ThreadArg *pArg = (ThreadArg *)arg;
 CDC dc;
 HDC hdc = ::GetDC(pArg->hwnd);
 dc.Attach(hdc);

해서 쓴다.

 

GetDC로 HWND에서 DC 객체만 뽑아 내면

이젠 dc로 그림 그리면, 화면에 그릴 수 있다.

 

예제)

더블 클릭을 하면, 스레드를 두개 만들어서, 사각형 크기를 늘려가는 프로그램

 

스레드 함수

 

UINT MyDraw(LPVOID arg)

{

       ThreadArg *pArg = (ThreadArg *)arg;

       CDC dc;

       HDC hdc = ::GetDC(pArg->hwnd);

       dc.Attach(hdc);

 

       int i, x, y;

       CBrush brush1(RGB(255, 0, 0));

       CBrush brush2(RGB(0, 0, 255));

 

       switch(pArg->type){

       case 1:

             dc.SelectObject(&brush1);

             x = 100;

             y = 100;

             for (i = 0; i<100000; i++){

                    int inc = i/200;

                    dc.Rectangle(x, y, x+inc, y+20);

             }

             break;

       case 2:

             dc.SelectObject(&brush2);

             x = 100;

             y = 200;

             for (i = 0; i<100000; i++){

                    int inc = i/200;

                    dc.Rectangle(x, y, x+inc, y+20);

             }

             break;

       }

 

       dc.Detach();

       ::ReleaseDC(pArg->hwnd, hdc);

 

       return 0;

}

 

뷰클레스 멤버 변수

 

       CExWorkerThread2Doc* GetDocument();

       CWinThread *pThread1, *pThread2;

       ThreadArg arg1, arg2;

 

 

뷰클레스 더블클릭 함수

void CExWorkerThread2View::OnLButtonDblClk(UINT nFlags, CPoint point)

{

       // 첫번째쓰레드생성(정지상태)

       arg1.hwnd = this->m_hWnd;

       arg1.type = 1;

       pThread1 = AfxBeginThread(MyDraw, &arg1,

             THREAD_PRIORITY_NORMAL, 0, 0);

 

       // 두번째쓰레드생성(정지상태)

       arg2.hwnd = this->m_hWnd;

       arg2.type = 2;

       pThread2 = AfxBeginThread(MyDraw, &arg2,

             THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);

 

       // 쓰레드우선순위레벨변경

       // pThread2->SetThreadPriority(THREAD_PRIORITY_ABOVE_NORMAL);

 

       // 두쓰레드를실행한다.

       pThread1->ResumeThread();

       pThread2->ResumeThread();

      

       CView::OnLButtonDblClk(nFlags, point);

}

 

 

결과화면

 

 

두개의 막대는 계속 증가하는대, 우선순위는 똑같이 설정 했으므로 상호 경쟁적으로 막대가 증가하게 된다.

 

프로그램 코드는 첨부했다.

ThreadEx.zip
0.02MB

 

 

[출처] MFC - 스레드 함수에 뷰클레스

의 DC객체 넘겨주는 방법|작성자 Andy

 

 

 

 

 

=======================

=======================

=======================

 

 

 

 

출처: http://blog.naver.com/PostView.nhn?blogId=lhyoup0360&logNo=220442204823&parentCategoryNo=31&categoryNo=&viewDate=&isShowPopularPosts=false&from=postView

 

 

MFC(프로젝트 생성, 스레드, 마우스 클릭하여 도형 그리기)

 

-MFC란?

 : C/C++ 은 프로그래밍 언어이다. 윈도우 운영체제에서 돌아가는 프로그램을 만들기 위해서는 

  Win32 API 라는 라이브러리를 사용해야 하는데, Win32 API 는 윈도우창을 띄우고, 버튼을 만드는 등의 

  윈도우창 기반의 여러 기능들이 있는 C함수들을 제공하고 있다.

   MFC 는 이러한 C 언어 기반의 윈도우 관련 함수들을 C++ 의 클래스로 구조화한 것다.

  즉, MFC 는 프로그래밍 언어가 아닌 C++ 언어로 만들어진 윈도우 관련 기능을 하는 클래스들의 집합이다다.

  MFC 프로그래밍 공부란 MFC에서 제공하는 클래스들의 사용방법을 익히는 것이라 할 수 있다.

 

 

▒프로젝트 생성

1. MFC 응용 프로그램을 클릭한 이름과 경로를 지정해주고 확인을 누른다

 

  

 

 // 프로젝트 이름을 설정해줄 때 한글과 숫자는 가능하면 쓰지 않는 것이 좋다. 

   경로에도 한글이 명시되면 오류가 생길 수 있다.

 

2. 다음 버튼을 클릭한다.

 

 

3. 아래와 같은 화면이 되도록 설정해준 뒤 다음을 클릭한다.

 

 

  

 //단일 문서, 다중 문서는 사용자에게 보여지는 인터페이스가 한개인지, 한개 이상인지에 대한 선택이고

   이번 실습은 대화상자를 기반으로 진행되기 때문에 대화 상자 기반을 선택한다

 //SDL에 대해 자세히 찾아보진 않았지만 대충 보안 개발 방법에 대한 투명한 제공을 위해 제작된 보안 프로세스 라고 한다.

 

 

4.아래와 같은 화면이 되도록 설정해준 뒤 다음을 클릭한다.

 

  // 초기화면에서는 정보 상자가 클릭 된 상태로 나올것인데 체크를 해제 하지 않는다면 사용하지 않는 클래스가

   한개 더 생겨서 귀찮아 진다

 

5.아래와 같은 화면이 되도록 설정해준 뒤 다음을 클릭한다.

 

 

6.마침을 클릭한다.

 

 

▒MFC에서 반드시 포함시켜야 하는 클래스

 

1.CWinApp  // 클래스를 C로 시작하는 이름으로 짓고 샆다면 C를 한번 더 써줘야 한다. ex)CCow ,CCar

 : 지난 시간 윈도우즈 프로그래밍에서 해야하는 것은 아래 세가지가 있었다. 

  1) window class 등록 --> InitApplication을 상속받아 재정의

  2) Main Window 생성 --> InitInstanc를 상속받아 재정의

  3)Message 처리   --> Run을 상속받아 재정의

MFC로 넘어와서는 이러한 기능들을 하는 함수들을 CLASS화 하여 통합 제공 하는데 그게 바로 CWinApp이다.

//InitApplication과 Run은 바꿀일이 없으나 InitInstace는 처음엔 텅 비어 있기 때문에 CwinApp에서 상속받아 CMyApp를

  만들어 InitInstance를 오버라이딩 하여 MainWindow를 만드는 작업을 해줘야 한다. 

  하지만 처음 프로젝트 생성시 

 

// 생성과 해제는 InitApplication -> InitInstance -> Run -> ExitInstance -> ExitApplication 순서로 진행된다.

 

//클래스화 되는 과정에 대해 세부적으로 알고싶다면 아래 주소를 확인해보길 바란다.

 http://blog.naver.com/tipsware?Redirect=Log&logNo=220070159066

 

 

2.CWnd

 : 윈도우 창을 관리하는 클래스다

  Cwnd는 기본적으로 아래와 같이 만들어준다. 편집을 하고 싶다면 위에서 설명한 것과 같이 

 직접적인 변경은 불가능하고 CWnd를 상속받아 CMyWnd를 생성하여 오버라이딩을 해줘야 한다.

 

 

CWnd를 상속받은 CFrameWnd클래스를 사용하면 CToolBar, CStatusBar 까지 관리할 수 있다.

리소스 워크샵에 가서 내가 사용하고싶은 툴바나 스테이터스 바에 대한 정보만 편집해주면 된다.

 

 

 

위 그림에서 CToolBar와 CstatusBar까지 포함한 영영을 Clientarea라고 하는데 왼쪽 위 가장자리의 좌표가

(0,0)이 되있는데 툴바,스테이터스가 생기게 된다면 좌표를 다시 계산해줘야되는 번거로움이 생긴다.

그래서 생긴 새로운 영역이 CView로 툴바와 스테이터스 바를 제외한 영역을 새로 지정하여 좌표를 계산해준다.

이 클래스 또한 CFrameWnd으로 관리 한다.

 

 

 

※프로세스와 스레드

 프로세스(Process)

 : 프로그램은 윈도우나 리눅스,맥OS같은 운영체제(OS)에 의해서 실행된다. 운영체제들은 프로그램을 실행할때 프로세스 단위로  관리한다.  하나의 프로그램은 하나의 프로세스에 해당된다

 

 스레드(Thread) 

  : 하나의 프로그램(프로세스)를 쪼개어서 보면 다수의 메서드(함수)로 구성되어 있다.  하나의 프로그램 내에서도 cpu는 한번에 하나의 메서드만을 실행시킬 수 있다. 하나의 프로그램내에서 실행되는 메서드를 쓰레드라 부른다.

  프로그램을 실행하면 스택영역의 메모리를 할당받고 스택영역에 올라오는 메세드를 하나씩 처리한다. 

메모리에서 차이를 살펴보면 프로세스는 독립적으로 실행되고, 자신만의 고유 메모리를 할당 받아 사용한다.

 반면에 스레드는 한 프로세스 내의 여러 흐름으로서 프로세스 내 주소 공간이나 자원을 공유해서 실행된다.

 그림으로 본다면 아래와 같다.

 

 

  -스레드의 장점과 단점

   장점 : 1. 시스템의 자원 소모가 줄어든다. 프로그램의 응답시간이 단축된다.

          2. 프로세스 간 통신 방법에 비해 스레드간의 통신 방법이 훨씬 간단하다.

   단점 : 1. 프로세스 밖에서 스레드 각각을 제어할 수 없다.

          2. 여러개의 스레드를 이용하는 프로그램을 작성하는 경우에는 주의 깊게 설계해야 한다.

             미묘한 시간차나 잘못된 변수를 공유함으로 써 오류가 발생할 수 있다.

          3. 프로그램 디버깅이 어렵다. 단일 프로세스 시스템에서는 효과를 기대하기 어렵다.

 

 

 

 

SDI(단일문장) = CWinApp + CFrameWnd + CView(사용자 인터페이스를 받고) + CDocument(자료구조를 가지고 있다)  

  //정상적인 프로그램은 자료구조와 파일 입출력이 반드시 있어야 하지만 사용의 불편화로 인해 향후 MS도 

   CDocument 사용을 선택사항으로 하였다.

                 

 

Dig(Diallog-based 대화상자 기반) = CWinApp + CDialog

 CDialog : 화면에 대화 상자를 표시하기 위해 사용되는 base 클래스. 

 

 

※솔루션 탐색기 용어

 ico =파일 아이콘

 rc = 리소스 파일

 stdafx = free compiled header로 컴파일 속도를 향상시키는 파일이다.

 

 

▒도형 생성하기

실습을 진행하기 전에 위에 프로젝트 생성을 설명하는 부분에서의 프로젝트 이름을 TestMFC로 변경하겠습니다.

이번 실습의 목표는 마우스 왼쪽버튼을 누를땐 사각형이, 컨트롤키를 누르며 왼쪽마우스 클릭시 원이 그려지도록한다.

 

1.TestMFC.cpp파일에 있는 InitInstance 함수로 이동한다.

  레지스트리에 계속 등록되어 윈도우가 느려지는것을 막기 위해 InitInstance의 하위 항목을 지워준다.

 

 

2. 프로젝트 -> 클래스 마법사  또는 단축키 Ctrl+Shift+x 버튼을 클릭한다

 

//주의할점은 클래스 이름을 CTestMFCApp가 아닌 CTestMFCDlg가 선택되도록 한다.

 

 왼쪽마우스 버튼을 클릭할때 메세지가 전달되도록 하기 위해 메시지로 이동하여 WM_LBUTTONDOWN을 검색한다.

 검색후 처리기 추가 버튼을 눌러준다. 그리고 확인버튼을 클릭한다.

 

 //위와 같이 전처리기를 추가하고 삭제할때는 실행취소버튼인 ctrl+z를 한번만 실행해서는 안된다.

  한군데만 추가되는것이 아니기 때문에 처리기 삭제버튼을 이용해 삭제해주던지 추가된 곳을 찾아다니며 모두 삭제해줘야한다.


 

3. 추가된 LBUTTONDOWN에 대한 추가 정보를 입력해준다

 

  //nFlags: 조합키 확인할 때 사용하는 변수, API에서 wParam같은 역할을 합니다.

  //point: 클릭된 좌표 들어오는 변수. API에서 lParam같은 역할을 합니다. 

 

 그림을 그리려면 DC가 필요한데 그림을 그리려는 윈도우는 DC를 허가받아서 사용해야한다.

윈도우에다 그림을 그리려면 윈도우에서 사용하는 DC 얻어와야 한다.

 저번시간에 진행했던 API에서는 HDC를 사용하고 API는 모든 걸 핸들 값으로 반환하기 때문에 먼저 DC의 핸들 값인 HDC 타입으로 변수선언하고, 특정 윈도우로부터 DC를 얻어오기 위해 GetDC함수를 사용 했다.

HDC h_dc = ::GetDC(m_hWnd);

//m_hWnd : 윈도우클래스는 자기가 관리하는 윈도우의 핸들을 멤버변수로 가지고 있는데 그 이름이다.

ReleaseDC(m_hWnd, h_dc);

//(m_hWnd, h_dc) : 어떤 윈도우에 어떤 DC를 없애겠다 명시한다.

 

 MFC에서는 CClientDC라는 클래스를 사용한다. client area에만 그림 그릴 수 있는 권한을 갖고 있다.

CWindowDC는 윈도우 전체에 다 그릴 수 있다.

CClientDC dc(this);

//GetDC와 ReleaseDC 두가지를 포함하고 있다.

//내가 만들 윈도우의 주소를 알 수 없으므로 자기참조포인터인 this 사용합니다.

//dc라는 객체가 생기고 this를 통해 이 윈도우에 그림을 그릴 수 있는 권한을 넘겨줍니다. 

 

  위 내용으로 입력해준다면 프로그램은 제대로 돌아갈지 몰라도 저번시간에도 언급했던 무효화 영역(Invalid Area)에 들어가면 그림이 사라지는 것을 확인 할 수 있을 것이다.

 이번실습은 WM_PAINT를 이용하여 무효화 영역에 들어가도 사라지지 않도록 진행해 보겠다.

 

 

4. 우선 TestMFC.cpp에서 Ctrl+k+o버튼을 눌러 소스파일에서 헤더파일로 전환 시킨다.

 그리고 아래와 같이 클릭할때 마다 x,y주소값과 그림이 사각형인지 타원인지 저장할 구조체 Clickinformation을 선언한다.

 최대 100번까지 클릭할 수 있도록 MAX_CLICK_COUNT를 선언하고 100번의 클릭 정보를 저장할수 있도록 m_pos_list 구조체배열 또한 선언해준다.

 m_click_count는 클릭 횟수를 기억한다.

 

 

5. TestMFC.cpp로 다시 이동하여 내용을 수정해준다

  마우스 왼쪽 버튼이 클릭될 때 마다 클릭 횟수(count)를 측정하며 클릭된 주소값을 m_pos_list의 x,y에 저장한다.

  또한 조합키 상태에 따른 그려진 도형을 m_pos_list의 flag에 저장한다.

  count가 100이 되면 return을 통해 종결시킨다.

 

6. 무효화 영역으로 인해 지워진 부분을 다시 그려주기 위해서는 WM_PAINT 메시지를 이용해야 한다.

  WM_PAINT 메세지가 발생하면 아래의 OnPaint가 실행되도록 설정되 있다.

 

if문장은 대화상자가 최소화 됐을때 실행되는 부분으로 위 양식으로 맞춰 준다.

 

  

 무효화 영역에 들어갔을때 DC를 받아와 그림이 지워지기전 m_pos_list에 저장 된 x,y좌표값과 flag값을 읽어 flag값이 1이었으면 타원, 0이었으면 사각형을 같은 주소값에 다시 그려넣어 준다.

 

※Flag성 메세지란?

 

     InvalidateRect 함수를 통해 무효화 된 영역은 WM_PAINT 메시지를 통해 다시 그려지게 됩니다.

 

     그렇다면 이는 'InvalidateRect 함수호출은 WM_PAINT 메시지를 발생시킨다'를 의미할까요?

 

     보통 윈도우에 메시지가 발생하면 이 메세지는 윈도우 메시지 큐에 들어가게 되고, WndProc는

     메세지 큐에 들어온 순서대로 메시지들을 처리하게 됩니다. 하지만 Flag성 메시지는 보통 메시지와는

     다른 처리방법을 가지고 있습니다.

 

     Flag성 메세지란 메세지가 발생하게 되면 처리되기 위해 메시지 큐에 들어가는 것이 아닌 처리되어야

     함을 의미하는 Flag만 설정하는 메세지를 의미합니다. Flag성 메시지는 GetMessage 함수가 메시지

     큐에 들어있는 메시지를 다 처리하고 난 후, Flag성 메세지들을 검사하게 되는데 Flag성 메세지의

     Flag가 설정되어 있다면 바로 처리 되게 됩니다.

 

     이와 같이 Flag성 메시지는 보통 메시지들과는 다른 처리 방법을 가지고 있고 보통 메시지들 보다

     처리되는 우선순위가 가장 낮음을 알 수 있습니다.

 

     WM_PAINT 메시지는 보통 메시지가 아닌 Flag성 메시지 입니다. 이는 다시 말해서 WM_PAINT

     메시지는 처리를 위해 다른 무언가에 의해 발생 되어지는 메시지가 아닌 처리되어야 할 필요성, Flag

     만 설정되고 메세지 큐안의 다른 메세지들 처리가 끝나면 처리되는 메세지 입니다. 

 

     UpdateWindow()함수를 호출한다면 Flag성 메시지가 발생 돼있으면 즉시 갱신할 수 있습니다.

     출처 : http://www.tipssoft.com/bulletin/board.php?bo_table=FAQ&wr_id=632

 

 //CPaintDC dc(this)는 flag성 메시지인 테이블의 paint값이 1일때 갱신해주는데 갱신해주고 나면 테이블의 paint 메시지값을 0으로 바꿔줌으로 써 onpaint함수가 계속해서 실행되지 않아 cpu소모를 막을 수 있다. CClientDC dc(this)는 0으로 바뀌지 않아 무한루프가 된다.

 

 //포인터 변수부분의 p_pos++->flag부분을 p_pos->flag로 써버리는 바람에 오랫동안 고생했는데 ++을 써주지 않는다면 m_pos_list[0]의 flag값만 계속 나오기 때문에 꼭 주의 하도록 하자.

위 과정을 모두 따라 왔으면 아래와 같이 실행 되는 것을 확인할 수 있을것이며 무효화 영역에 들어가도 갱신되는 것을 확인할 수 있다. 

 

[출처] MFC(프로젝트 생성, 스레드, 마우스 클릭하여 도형 그리기)|작성자 이광운

[출처] MFC(프로젝트 생성, 스레드, 마우스 클릭하여 도형 그리기)|작성자 

 

 

 

반응형

 

728x90

 

 

 

=======================

=======================

=======================

 

 

 

 

출처: http://kimyongyu.com/168

 

 

쓰레드 동기화 오브젝트

 (Thread Synchronization Objects)

 

 쓰레드가 2개 이상 실행될 때 여러 가지 변수가 있습니다.

하나의 공유자원(예를 들어 동시에 접근하는 변수에 접근할 때파일 입출력 이나 디바이스I/O작업을 할 때 동기화 오브젝트가 필요합니다.

동기화 오브젝트 없이 쓰레드가 공유 자원을 사용할 때 공유자원이 원치 않은 값이 될수 있고, I/O작업 시 쓰레드가 I/O작업이 끝날 때 까지 무한정 블로킹(blocking : 특정 함수가 리턴 될 때 까지 기다림)현상이 발생할 수 있습니다.

동기화 오브젝트를 사용하여 다중 쓰레드에서 어떻게 안전 하게 공유자원에 접근하고 다른 쓰레드간의 실행 순서등을 조작하는지에 대해 알아 보겠습니다.

동기화 오브젝트는 유저 모드와 커널 모드로 분류할 수 있습니다.

  유저 모드

 유저 모드는 현재 프로세스/쓰레드 내의 상태를 말합니다.

유저모드에서는 커널 오브젝트(프로세스파일디바이스 등)으로 바로 접근을 할 수 없고커널 오브젝트로 접근 시 시스템에 의해 변환 작업이 이루어집니다.

이런 변환 작업은 시간을 많이 걸리는 작업이기 때문에 유저 모드가 커널 모드 보다 속도가 빠릅니다.

유저 모드 동기화 방법은 코드레벨에서 동기화 하는 방법을 이야기 합니다.

유저모드에서 동기화는 사용하기 쉽고 커널 모드 동기화 함수들에 비해 속도가 빠른 장점이 있지만 커널 오브젝트(파일 I/O, 프로세스쓰레드 등)의 동기화는 불가능하다는 단점이 있습니다.

 Interlocked

 Interlocked 함수들은 다중 쓰레드에서 공유변수들을 안전하게 1씩 증가/감소특정값을 증가 , 비트 연산을 할 수 있습니다.

Intlocked 함수는 사용하기 쉬우므로 길게 설명은 하지 않겠습니다.

아래 사이트를 참조하시길 바랍니다.

http://msdn2.microsoft.com/en-us/library/ms686360(VS.85).aspx

 

 크리티컬 섹션(CRITICAL SECTION)

 크리티컬 섹션은 특정 코드영역을 쓰레드가 동시에 실행되는 것을 막아 줍니다.

아래는 크리티컬 섹션 관련 함수들입니다.

 

DeleteCriticalSection 크리티컬 섹션 오브젝트를 삭제 합니다.
InitializeCriticalSection 크리티컬 섹션 오브젝트를 초기화 합니다.
InitializeCriticalSectionAndSpinCount 크리티컬 섹션 오브젝트를 초기화 하고 스핀 카운트를 설정합니다.
InitializeCriticalSectionEx 크리티컬 섹션을 초기화하고 스핀카운트 설정부가기능을 설정합니다.
LeaveCriticalSection 크리티컬 섹션 오브젝트 권한을 해제합니다..
SetCriticalSectionSpinCount 특정 크리티컬 섹션 오브젝트 스핀카운트를 설정합니다.
TryEnterCriticalSection 블로킹(Wait) 되지 않고 크리티컬 섹션 오브젝트의 권한을 요청합니다.

 

InitializeCriticalSectionAndSpinCount 함수는 스핀 카운트를 두어서 쓰레드가 크리티컬 섹션 오브젝트를 획득하지 못하면 Wait상태로 일정 시간(스핀카운트루프를 돌아 크리티컬 섹션오브젝트가 해제 되었는지 체크합니다.

해제되지 않았으면 Sleep하게 됩니다.

이 함수는 멀티 프로세서 환경에서만 유효하며스핀카운터는 4000을 추천(Windows Via C/C++)하지만 자신의 환경에서 값을 바꾸어 가며 테스트 해보길 권장합니다.

 

#include <Windows.h>

CRITICAL_SECTION g_cs;

//크리티컬 섹션 오브젝트를 초기화 합니다.
void InitCriticalSection()
{
        InitializeCriticalSection(&g_cs);
}


unsigned _stdcall CallThreadHandlerProc(void *pThreadHandler)
{
        while (bExit == FALSE)
        {
               if(TryEnterCriticalSection(&g_cs))
               {

                       //쓰레드 작업을 수행 합니다.

                       //수행 하고 LeaveCriticalSection 함수를 호출

                       //하여 크리티컬 섹션 오브젝트를 해제합니다.

 

                       LeaveCriticalSection(&g_cs);
               }
               else
               {

                        // 크리티컬 섹션 오브젝트 획득에 실패 시 수행할
                        // 작업을 선언합니다.
                        // SwitchToThread함수를 호출하여 다른 쓰레드로
                        // 스위칭 합니다.
                       SwitchToThread();                    
               }             
        }

        DWORD exitCode;
        GetExitCodeThread(InputThrd, &exitCode);
        _endthreadex(exitCode);

        return 0;
}

 

**Sleep() 함수와 SwitchToThread()함수는 디스패쳐가 다른 쓰레드로 스케쥴 하도록 합니다.

차이점은 Sleep()함수는 현재 쓰레드보다 우선순위가 같거나 높은 쓰레드가 없으면 쓰레드 전체를 리스케쥴링(rescheduling)합니다.

  

Slim Reader/Writer Locks

 Slim reader/writer (SRW) locks  하나의 프로세스내의 쓰레드들이 공유자원을 동기화 할 수 있습니다.

아주 작은 메모리를 차지하면서 속도도 빠릅니다.

Reader 쓰레드는 공유자원을 읽고 Writer 쓰레드는 공유자원에 쓰기 작업을 할수 있습니다.

다중 쓰레드가 공유자원을 읽고 쓰기를  크리티컬 섹션과 뮤텍스 같은 상호배제 오브젝트(exclusive locks)들은 reader 쓰레드는 계속 돌고 writer 쓰레드는 거의 돌지 못하면 병목현상(bottle neck) 발생   있습니다.

SRW locks 공유자원에 접근 할 수 있는 두 가지 모드를 제공합니다

·Shared mode : 읽는 작업을 하는 쓰레드가 여러 개일 때 공유자원을 읽기 전용으로 접근할 수 있도록 해서 동시 다발적으로 작업을 할 수 있도록 합니다.  만약 읽는 작업이 쓰는 작업을 초과 할 경우, 성능과 처리량은 크리티컬 섹션과 동일하게 됩니다. 
·Exclusive mode : 읽기/쓰기 쓰레드는 하나의 쓰레드만 접근 할 수 있습니다.  Exclusive mode로 락이 걸려지면 다른 쓰레드들은 공유자원에 접근할 수 없습니다.

하나의 SRW lock 두 가지 모드를 동시에 가질 수 있습니다읽는 쓰레드는 Shared mode 쓰는 쓰레드는 Exclusive mode 작업을   있습니다.  어떤 쓰레드가 소유권을 먼저 가질지는 알수 없습니다. SRW Locks 공정하거나 선입선출(First In First Out : FIFO)방식이 아닙니다.

SRW lock 포인터 크기를 가집니다장점은 속도가 빠르고 lock상태의 변환이 빠르다는  입니다.

단점은 아주 작은 상태 정보만 저장이 되어 재귀적으로 SRW locks 가질  없습니다 Shared Mode 쓰레드가 Shred Mode 변환 될수 없습니다.

SWR Locks Windows Server 2008,Vista 에서만 사용이 가능합니다.

아래는 SRW lock 함수들 입니다.

SRW lock function Description
AcquireSRWLockExclusive SRW lock  exclusive mode 얻습니다.
AcquireSRWLockShared SRW lock shared mode 얻습니다.
InitializeSRWLock SRW lock 초기화 합니다.
ReleaseSRWLockExclusive exclusive mode SRW lock 해제 합니다.
ReleaseSRWLockShared shared mode SRW lock 해제 합니다.
SleepConditionVariableSRW SRW작업이 완료 될때까지 Sleep 합니다.

 커널 모드

유저 모드에서 커널 오브젝트에 접근을 할 때 시스템은 커널 모드로 변환을 합니다.

커널 오브젝트에는 File, Event, Mutex, Semaphore, Process, Waitable Timer, Job, Thread 가 있습니다.

커널 모드에서 동기화는 커널 오브젝트가 non-Signal인지 Signal 상태인지를 보고 쓰레드를 스케쥴링합니다.

커널 오브젝트가 signal상태이면 쓰레드가 돌아갈 준비가 된 상태이고, non-signal 상태이면 쓰레드는 기다림(Wait)상태 입니다.

 

이벤트(EVENT) 
 가장 많이 보편화되고 많이 쓰는 다중 쓰레드 동기화 오브젝트가 이벤트가 아닌가 생각이 됩니다.

이벤트로 다중 쓰레드 동기화 하는 방법에는 두 가지 방법이 있습니다.

개발자가 직접 이벤트를 수동으로 signal/non-signal 상태로 변환 하는 것과 시스템이 자동으로 이벤트를 signal/non-signal상태로 변환 하는 방법입니다.

이벤트를 기다리는 방법은 WaitForSingleObjec/WaitForSingleObject 함수를 사용합니다이벤트가 non-signal상태가 될 때까지 쓰레드는 Wait상태로 됩니다.

이벤트 및 모든 커널 오브젝트는 사용이 끝나면 CloseHandle로 사용을 종료 해야 합니다.

 

Event function Description
CreateEvent 이벤트 오브젝트를 생성하거나 오픈 합니다.
CreateEventEx 이벤트 오브젝트를 생성하거나 오픈 합니다.(접근 권한을 줄수 있습니다.)
OpenEvent 존재하는 이름이 있는 이벤트 오브젝트를 오픈 합니다.
PulseEvent 특정 이벤트 오브젝트를 signal 상태로 바꾸고 일정시간  non-signal상태로 바끕니다.
ResetEvent 이벤트 오브젝트를 non-signal 상태로 놓습니다.        
SetEvent 이벤트 오브젝트를 signal 상태로 놓습니다.          


 간단 하게 SDI 프로그램에서 수동 모드(passive mode) 이벤트를 이용하여 사각형과 원을 그리는 멀트 쓰레드 프로그램을 보겠습니다. 



BOOL CEventSampleView::PreCreateWindow(CREATESTRUCT& cs)
{
        // TODO: CREATESTRUCT cs를 수정하여 여기에서
        //  Window 클래스 또는 스타일을 수정합니다.
        srand( (unsigned)time( NULL ) );

 

        //종료조건을 초기화합니다.
        m_bContinue = TRUE;

        //수동모드 이벤트 오브젝트를 생성합니다.
        m_DrawEvent = CreateEvent(0, TRUE, TRUE, _T("DrawEvent"));

        // 시그널 상태로 둡니다.
        SetEvent(m_DrawEvent);

        return CView::PreCreateWindow(cs);
}

 

/*OnDraw 메시지 핸들러에서 사각형을 그리는 쓰레드와
원을 그리는 쓰레드를 생성합니다.*/

void CEventSampleView::OnDraw(CDC* /*pDC*/)
{

        CEventSampleDoc* pDoc = GetDocument();
        ASSERT_VALID(pDoc);

        if (!pDoc)
               return;

        m_RectThrd = AfxBeginThread(RectThreadProc,reinterpret_cast<LPVOID>(this));
        m_CricleThrd = AfxBeginThread(CircleThreadProc, reinterpret_cast<LPVOID>(this));
        // TODO: 여기에 원시 데이터에 대한 그리기 코드를 추가합니다.
}

 

/*사각형을 그리는 쓰레드 프로시저*/
UINT CEventSampleView::RectThreadProc(__in LPVOID lpParameter)
{

        CEventSampleView* pView = reinterpret_cast<CEventSampleView*>(lpParameter);

        int x=0,y=0, cx=0, cy=0;
        cx = 100;
        cy = 100;

        while(pView->m_bContinue)
        {

               //시그널상태가 될 때까지 기다립니다.
               WaitForSingleObject(pView->m_DrawEvent, INFINITE);

               //이벤트 오브젝트를 받아오면 넌시그널 상태로 둡니다.
               ResetEvent(pView->m_DrawEvent);

              

               HDC hDC = ::GetDC(pView->GetSafeHwnd());
               x= rand()%300;
               y = rand()%300;

               ::Rectangle(hDC,x, y, x+cx, y+cy);

               ::ReleaseDC(pView->GetSafeHwnd(),hDC);

               // 작업이 끝나면 이벤트를 시그널상태로 두어 다음 쓰레드가 가질 수

               // 있도록 합니다.

               SetEvent(pView->m_DrawEvent);
        }

        DWORD dCode=0;
        GetExitCodeThread(pView->m_CricleThrd->m_hThread, &dCode);
        AfxEndThread(dCode, TRUE);

        return 0;
} 


/*원을 그리는 쓰레드 프로시저*/
UINT CEventSampleView::CircleThreadProc(__in LPVOID lpParameter)
{
        CEventSampleView* pView = reinterpret_cast<CEventSampleView*>(lpParameter);

        int x=0,y=0, cx=0, cy=0;
        cx = 100;
        cy = 100;

        while(pView->m_bContinue)
        {

               WaitForSingleObject(pView->m_DrawEvent, INFINITE);

               ResetEvent(pView->m_DrawEvent);

               HDC hDC = ::GetDC(pView->GetSafeHwnd());

               x= rand()%300;

               y = rand()%300;

               ::Ellipse(hDC,x, y, x+cx, y+cy);

               ::ReleaseDC(pView->GetSafeHwnd(),hDC);

               SetEvent(pView->m_DrawEvent);
        }

        DWORD dCode=0;
        GetExitCodeThread(pView->m_CricleThrd->m_hThread, &dCode);
        AfxEndThread(dCode, TRUE);

        return 0;

} 


//프로그램이 종료할 때 종료조건을 맞춰 주고 쓰레드 종료 메시지를 주어 정상적으로
//종료하도록 하고, 이벤트 핸들을 닫습니다.
void CEventSampleView::OnDestroy()
{
        CView::OnDestroy();

        m_bContinue = FALSE;

        DWORD dExitCode = 0;

        GetExitCodeThread(m_CricleThrd->m_hThread,&dExitCode);
        PostQuitMessage(dExitCode);


        GetExitCodeThread(m_RectThrd->m_hThread,&dExitCode);
        PostQuitMessage(dExitCode);

 
        CloseHandle(m_DrawEvent);
}


 
뮤텍스(MUTEX) 

뮤텍스는 하나의 공유자원에 대한 상호 배타(mutual exclusive)적으로 동기화 하는 방법입니다.  뮤텍스는 서로 다른 프로세스의 쓰레드의 동기화를 할수 있습니다.  이 특성을 이용해서 보통 하나 이상의 프로그램을 실행하기 위해 뮤텍스를 이용합니다.(이걸 깨는 방법도 있죠.)

뮤텍스는 다음과 같은 규칙이 있습니다. 

Ø  쓰레드의 ID 0(유효하지 않은 쓰레드 ID)이면 뮤텍스의 소유권은 어느 쓰레드에게도 없다는 의미 이고 뮤텍스 오브젝트는 시그널된 상태입니다.

Ø  쓰레드 ID 0이 아닌 값이면해당쓰레드(생성 시킨 쓰레드)가 소유권을 가지면 뮤텍스 오브젝트는 non-signal상태 입니다.

Ø  다른 커널 오브젝트와는 달리 뮤텍스는 소유권(thread ownership)이라는 개념이 있습니다. 

뮤텍스를 해제할 때(ReleaseMutex), 쓰레드 ID와 생성할 때 설정한 쓰레드의 ID가 맞지 않으면 해제에 실패하고 시스템은 해당 뮤텍스의 시그널 상태를 기다리는 다른 쓰레드를 스케쥴링 합니다.

뮤텍스의 소유권을 가진 쓰레드가 뮤텍스를 해제 하지 않고 종료 되면시스템은 해당 뮤텍스를 “abandoned”상태로 두고이 뮤텍스를 기다리는 쓰레드를 찾아 기다리고 있는 쓰레드에 뮤텍스의 소유권을 주고 해당 쓰레드를 스케쥴링 합니다.

 

Mutex function Description
CreateMutex 뮤텍스 오브젝트를 생성하거나 오픈 합니다.
CreateMutexEx 뮤텍스 오브젝트를 생성하거나 오픈합니다접근 권한 속성을   있습니다.
OpenMutex 이름이 있는 뮤텍스 오브젝트를 오픈 합니다.
ReleaseMutex 뮤텍스 오브젝트를 해제합니다.

 http://msdn2.microsoft.com/en-us/library/ms686927(VS.85).aspx

  
세마포어(SEMAPHORE)

 세마포어는 공유 자원의 카운팅의 용도로 사용합니다.

세마포어는 사용 개수(usage count)이외에 signed 32비트 값 2개를 더 가지고 있습니다.

Ø  최대 리소스 카운트(maximum resource count) : 세마포어가 관리할 수 있는 최대 리소스의 개수.

Ø  현재 리소스 카운트(current resource count) : 현재 사용 가능한 리소스의 개수.

세마포어는 다음과 같은 규칙을 가지고 동작을 합니다.

Ø  현재 리소스 카운터가 0보다 크면(>0) 세마포어 오브젝트는 signal 상태입니다.

Ø  현재 리소스 카운터가 0이면세마포어 오브젝트는 non-signal상태입니다. 

Ø  시스템은 현재 리소스카운터를 – 값이 되지 않도록 합니다.

Ø  현재 리소스 카운터는 최대 리소스 카운터 보다 클 수 없습니다.

 

Semaphore function Description
CreateSemaphore 세마포어를 생성/오픈 합니다.
CreateSemaphoreEx 세마포어를 생성/오픈 합니다접근 권한을   있습니다.
OpenSemaphore 이름이 있는 세마포어 오브젝트를 오픈합니다.
ReleaseSemaphore 사용 가능한 리소스 개수를 증가 시킵니다..

 세마포어 사용 예는 아래 사이트를 참조 하세요.

http://msdn2.microsoft.com/en-us/library/ms686946(VS.85).aspx


Waitable Timer 

Waitable Timer 오브젝트는 특정 시간이 되면 오브젝트가 시드널 됩니다.

특정 시간 마다 어떤 동작을 해야 할 때 사용할 수 있습니다. 

Waitable-timer function Description
CancelWaitableTimer Waitable Timer 오브젝트를 비활성화 시킵니다.
CreateWaitableTimer Waitable Timer 생성하거나 오픈 합니다.
CreateWaitableTimerEx Waitable Timer 생성/오픈 합니다.
OpenWaitableTimer 이름이 붙여진 Waitable Timer 오브젝트를 오픈 합니다.
SetWaitableTimer Waitable Timer 오브젝트를 활성화 시키거나, Waitable Timer 오브젝트가 시그널 되었을  완료 통보 프로시져를 등록   있습니다..
TimerAPCProc SetWaitableTimer 함수로 등로한 완료 통보 프로시져 선언.

 

Timer-queue Timer

 Timer queue Timer 오브젝트는 일정시간이 지나면 시그널 되는 동작은 Waitable Timer 와 같습니다.

Timer Queue Timer 오브젝트는 일정 시간이 지나면 시그널 되는 오브젝트이고, Timer Queue오브젝트가 Timer Queue Timer 오브젝트를 큐 형태로 관리하면서

해당 프로시저를 호출 합니다.

 

 

위의 그림은 Timer Queue오브젝트가 Timer Queue Timer 오브젝트를 관리하고,

Timer Queue Timer가 시그널 되면 해당 프로시저를 호출하는 모습입니다.

 

Timer-queue timer function Description
ChangeTimerQueueTimer Timer queue timer 오브젝트의 속성을 변경 합니다..
CreateTimerQueue Timer Queue 오브젝트를 생성합니다.
CreateTimerQueueTimer Timer Queue Timer 오브젝트를 생성합니다.
DeleteTimerQueue 타이머  오브젝트를 삭제 합니다.
DeleteTimerQueueEx 타이머  오브젝트를 삭제 합니다.
DeleteTimerQueueTimer Timer Queue  있는 Timer Queue Timer 오브젝트를 삭제 합니다.

 

쓰레드간 통신

  마지막으로 쓰레드간 통신 하는 방법을 알아 보겠습니다.

윈도우 프로그래밍을 하면 윈도우에 SendMessage/PostMessage 함수로 메시지를 보내 듯이 쓰레드에 메시지를 보내고 받으면서 쓰레드간 통신을 할 수 있습니다.

쓰레드에 메시지를 보내는 함수는 PostThreadMessage 입니다

BOOL PostThreadMessage(       
    DWORD idThread,   
/*해당 쓰레드 ID*/

    UINT Msg,       /*메시지 ID*/

    WPARAM wParam,    /*메시지를 받는 쓰레드로 넘겨주는 WPARAM 인자*/

    LPARAM lParam     /* 메시지를 받는 쓰레드로 넘겨주는 LPARAM 인자*/

);

  
PostThread
로 보낸 메시지는 쓰레드 프로시저에서 PeekMessage/GetMessage로 받을 수 있습니다.

http://msdn2.microsoft.com/en-us/library/ms644936(VS.85).aspx

BOOL GetMessage(       
    LPMSG lpMsg,           
//MSG 구조체의 포인터 타입

    HWND hWnd,             //윈도우 핸들

    UINT wMsgFilterMin,    //필터링할 최소 메시지 ID

    UINT wMsgFilterMax     //필터링할 최대 메시지 ID

);

 

http://msdn2.microsoft.com/en-us/library/ms644943.aspx

 

BOOL PeekMessage(       
    LPMSG lpMsg,           
//MSG 구조체의 포인터 타입

    HWND hWnd,             //윈도우 핸들

    UINT wMsgFilterMin,    //필터링할 최소 메시지 ID

    UINT wMsgFilterMax,    //필터링할 최대 메시지 ID

    UINT wRemoveMsg        //메시지 큐에 해당 메시지를 지울지 안지울지 설정

);

  
PeekMessage
와 GetMessage의 차이점은, GetMessage는 메시지가 메시지 큐에 들어 올 때가지 블록 되고 Peek메시지는 메시지가 없으면 FALSE를 리턴 합니다.

더 자세한 사항은 차고 사이트를 참고 하세요.

 아래 예제 코드는 쓰레드 프로시져에서 메시지를 확인하고다른 작업을 수행 하는 예제 코드입니다

unsigned _stdcall CallThreadHandlerProc(void * pThreadHandler) {
    while (bExit == FALSE) {
        MSG msg;
        BOOL res = PeekMessage(& msg, NULL, 0, 0, PM_REMOVE);
        if (res) { // 0x404메세지 가 들어오면 특정 작업 수행
            if (msg.message = 0x404) {
                std::cout << "Message Received" << std::endl;
            }
        }
        // 다른 작업 수행
    }
    DWORD exitCode;
    GetExitCodeThread(InputThrd, & exitCode);
    _endthreadex(exitCode);
    return 0;
}

//.........................................................

//다른 쓰레드에서 쓰레드에 메시지를 보냅니다.
BOOL res = PostThreadMessage(ID1, 0x00404, 0,0 );


참고 사이트 및 서적

Windows Via C/C++

http://msdn2.microsoft.com/en-us/library/aa904937(VS.85).aspx

http://windows-programming.suite101.com/article.cfm/win32_message_processing_primer

 

*출처 :

http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=51&MAEULNo=20&no=8026&ref=8026


출처: http://kimyongyu.com/168 [김용유 닷컴(kimyongyu.com)]

 

 

 

 

=======================

=======================

=======================

 

 

 

 

출처: http://system.tistory.com/category/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D/MFC

 

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(멀티 쓰레딩)|작성자 엘리트

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://darkblitz.tistory.com/227

 

 

1. gluBuild2DMipmaps vs. glTexImage2D

OpenGL에서 제공하는 gluBuild2DMipmaps는 내부적으로 gluScaleImage를 사용하여 mipmap을 생성한다고 한다. 따라서 glTexImage2D와 달리 입력 영상의 크기에 상관없이 사용이 가능하나, 이 때문에 glTexImage2D에 비해 매우 느리다고 한다.

 

2. OpenGL에서 RC(Rendering Context)는 thread간에 공유가 되지 않는다. 따라서 각 Thread마다 서로 다른 RC를 사용하여야 하고, 여기서 texture나 display list 등의 리소스를 공유하고자 한다면 이를 위해서는 wglShareLists를 사용하여야 한다. 그리고 Multi-thread 환경에서는 각 thread마다 glBindTexture 앞 뒤로 critical section 등을 이용하여 동기화를 해주어야 한다.

 

Reference

http://www.gpgstudy.com/forum/viewtopic.php?t=1165&sid=dd746a3230ee36248ae310530290dfde

http://blog.naver.com/baboneo?Redirect=Log&logNo=80007487701



출처: http://darkblitz.tistory.com/227 [긍정적 사고]

 

 

 

=======================

=======================

=======================

 

 

 

 

출처: http://abekdh.tistory.com/14

MFC의 CWinThread 이용시의 MESSAGE 처리

 

opencv를 이용하여 MFC로 캠 2개를 돌리는 프로그램을 구현중이었습니다.

캡쳐 이미지를 계속해서 화면에 뿌리기 위해서 캠 수만큼의 스레드를 돌리도록 설계를 하였습니다.

 

간편하게 MFC에서 스레드를 돌리는 방법으로는, worker thread를 이용하는 것입니다.

worker thread는, AfxBeginThread API와 전역 함수(혹은 클래스 정적 함수)를 이용하는 것인데,

전역 함수를 쓰다보니 자연스레 전역 변수를 사용하게 되버리고,

java에 익숙한 MFC 초짜인 저로썬 왠지 맘에 들지 않더군요.-_-;;

 

그래서

변수들은 인스턴스가 들고 다녀야지!! 객체지향스럽게!!

란 생각으로 눈을 돌린 쪽은 user interface thread 입니다.

 

user interface thread는 CWinThread를 상속받아서 사용하는 것으로,

thread 내부에 변수나 사용자 정의 함수등을 추가할 수 있으며,

MESSAGE 처리를 지원한다는 장점이 있더군요!!

 

이를 사용하기 위해선 MFC 의 매크로 함수를 여기 저기 적어줘야합니다.

구글링을 해보면 사용법들이 쭉쭉 나오는데 대충만 정리해보자면,

 

1. class 선언부에(헤더파일) 

DECLARE_DYNCREATE(클래스명)

 

2. class 구현부에(cpp파일)

IMPLEMENT_DYNCREATE(클래스명, CWinThread)

 

을 써주는것부터 시작되어

 

virtual int Run();

 

virtual BOOL InitInstance();

virtual int ExitInstance();

 

등을 가상함수로 오버라이딩 해야 한다더군요.

 

(InitInstance : 스레드 시작시 호출. 초기화 개념. return TRUE 를 해줘야 스레드가 시작된다.

 ExitInstance : 스레드 종료시 호출. return의 의미 아직 찾아보지 않았다.

 Run : 스레드 시작부. 필수는 아님. 이유는? CWinThread 에는 이미 Run이 구현되어 있기 때문)

 

스레드를 돌리는 방법은

 

CWinThread *변수 = AfxBeginThread(RUNTIME_CLASS(클래스명));

으로 실행하거나

클래스명 *변수 = new CamPlayer();

변수->CreateThread();

로 실행할 수 있습니다.

 

갠적으로, 아래것이 더 좋더군요.

이유인 즉슨, 포인터 타입이 CWinThread가 아닌, 내가 만든 클래스의 포인터이기 때문에

직접 추가한 함수나 변수를 손쉽게 호출 및 접근할 수 있기 때문이죠.

(둘의 차이가 어떻게 나는지 더 나는지는 찾아보지 않았습니다)

 

이제 여기서부터 대대적인 삽질이 시작됩니다.

 

기왕 CWinThread로 가는것, MESSAGE를 적극 활용해보기 위하여 아래와 같이 정의하였습니다.

 

// ..h

#define WM_INIT_CAM (WM_USER+1)

#define WM_START_CAM (WM_USER+2)

#define WM_END_CAM (WM_USER+3)

 

// ..cpp

BEGIN_MESSAGE_MAP(CamPlayer, CWinThread)

//{{AFX_MSG_MAP(CMyWinThread)

ON_THREAD_MESSAGE(WM_INIT_CAM, OnInitCam)

ON_THREAD_MESSAGE(WM_START_CAM, OnStartCam)

ON_THREAD_MESSAGE(WM_END_CAM, OnEndCam)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

 

// ..h 의 class 선언부

DECLARE_MESSAGE_MAP()

afx_msg void OnInitCam(WPARAM wParam, LPARAM lParam);

afx_msg void OnStartCam(WPARAM wParam, LPARAM lParam);

afx_msg void OnEndCam(WPARAM wParam, LPARAM lParam);

 

그리고 다열로그에서 열심히 메시지를 날렸죠.

player1->PostThreadMessage(WM_END_CAM, NULL, NULL);

 

그런데, thread 가 메시지를 못받더군요. 왜? 왜?

처음 써보는거니, 잘못썼나 싶어서 별 짓을 다 해봤습니다. 안됩니다.

계속해서 삽질했습니다. 계속 안됩니다.

 

결국 알아냈습니다;;

이유는, Run 을 오버라이딩했기 때문;;;

Run을 지우니 비로소 메시지를 받기 시작합니다.

바로 CWinThread의 Run() 함수가 메시지를 처리하고 있었던 것이지요.

오버라이딩한 Run 내부에 CWinThread::Run() 을 추가해주니 잘 됩니다.

 

한번 Run함수를 MSDN 에서 찾아보았습니다.(여기)

 

Run acquires and dispatches Windows messages until the application receives a WM_QUIT message. If the thread's message queue currently contains no messages, Run calls OnIdle to perform idle-time processing. Incoming messages go to the PreTranslateMessage member function for special processing and then to the Windows function TranslateMessage for standard keyboard translation. Finally, the DispatchMessageWindows function is called.
Run is rarely overridden, but you can override it to implement special behavior.
This member function is used only in user-interface threads.


대충 요악해보면, WM_QUIT 메시지를 받기 전까지 잘 돌고, 메시지 없으면 OnIdle 수행한다는 말인데 허걱!!

 

허걱.....

Run is rarely overridden

 

이런 낭패가;;;;;

많은 웹상의 설명이나 예제들이 run을 구현하라고 되있더군요.

(사실 run을 구현할라믄, 그냥 편하게 worker thread를 쓰면 되는 겁니다.)

심지어 영어로 저 문장을 써놓고 "Run은 거의 오버라이드 한다" 라고 오역되있는 것도 보았습니다 -_-;;

역시, 웹상의 자료를 무조건 신뢰하면 안된다는걸 뼈저리게 느꼈습니다.

 

 

 

=======================

=======================

=======================

 

 

 

 

출처: http://devkyu.tistory.com/506

쓰레드(Thread) 예제, AfxBeginThread(),  종료 대기

 

* 쓰레드 생성

- AfxBeginThread 권장 

- CWinThread::m_bAutoDelete 

기본은 TRUE, 자동으로 개체(핸들포함)가 삭제됨 <- 권장

FALSE로 설정하려면 CREATE_SUSPENED 로 시작해서 설정후 ResumeThread()해줘야함

해제시 핸들을 닫으면 안되고 CWinThread개체를 delete 해야 함

 

- AfxEndThread()는 강제 종료 함수로 권장 안함

- CreateThread() 권장 안함

 

* 쓰레드 종료 대기
    - WaitForSingleObject 사용 권장

    - GetExitCodeThread 는 bAutoDelete = TRUE시 문제 있음

- CreateThread()때만 사용, 하지만 권장 안함

 

 

//=======================================
//AfxBeginThread

 

//시작
CWinThread * CSimpleTestDlg::MFC_TrdStart( bool _bAutoDel)
{
    char *str = "MFC_TrdStart"; g_pstr[0]=str;m_pstr[0] = str;
    //쓰레드 생성
    m_bStop=false;
    CWinThread *pWThd=0;
    //
    if( _bAutoDel == false){
        _DbgStr(_T("MFC_TrdStart- 자동삭제=false"));
        //값을 바꾸기위해서는 멈춤->설정->시작
        pWThd = AfxBeginThread( MFC_TrdFunc, this, 0, 0, CREATE_SUSPENDED  );//멈춤    
        pWThd->m_bAutoDelete = FALSE; //기본값은 TRUE, 설정
        pWThd->ResumeThread();//시작
    }else{
        _DbgStr(_T("MFC_TrdStart- 자동삭제=true"));
        pWThd = AfxBeginThread( MFC_TrdFunc, this, 0, 0, 0  );//<- 권장
    }

    if( !pWThd )
        return 0;

    m_hThread = pWThd->m_hThread;
    
    _DbgStr(_T("MFC_TrdStart- 끝=%X"), m_hThread);
    return pWThd;
}

 

//쓰레드 트리거 함수
UINT __cdecl CSimpleTestDlg::MFC_TrdFunc( LPVOID pParam )
{
    char *str = "MFC_TrdFunc";g_pstr[1]=str;
    CSimpleTestDlg *pClass = (CSimpleTestDlg *)pParam;
    pClass->MFC_TrdProc();
    _DbgStr(_T("쓰레드 종료 MFC_TrdFunc"));//MFC_TrdProc()에 AfxEndThread()가 있으면 여기까지 실행이 안됨
    return 1;
}

 

//실제 쓰레드 동작
int CSimpleTestDlg::MFC_TrdProc()
{
    char *str = "MFC_TrdProc";g_pstr[2]=str; m_pstr[2] = str;
    while(1)
    {
        if( m_bStop ){
            _DbgStr(_T("쓰레드 종료신호 MFC_TrdProc"));
            break;
        }

        Sleep(1000);
        _DbgStr(_T("쓰레드 실행중 MFC_TrdProc"));
    }

    _DbgStr(_T("쓰레드 종료 MFC_TrdProc-1-%d"), (UINT)GetTime() );
    //AfxEndThread(0);//쓰레드가 강제 종료됨, 사용하면 안됨 TerminateThread()와 같음
    _DbgStr(_T("쓰레드 종료 MFC_TrdProc-2-%d"), (UINT)GetTime() );//
    
    return 1;
}


//===============================================
//쓰레드 종료를 기다린다
//WaitForSingleObject 사용 권장

// CreadThread()로 시작한 쓰레드만 대기, AfxBeginThread() 로 시작한 쓰레드는 사용하면 안됨
int WaitThreadEndH(HANDLE _hThread, int _nWaitMilSec ,  LPCTSTR _sMsg, bool _bClose)
{
    if( !_hThread ) return 0;
    _DbgStr(_T("WaitThreadEnd-시작-%s-%X-%d"),_sMsg, _hThread, GetTickCount() );
    //bFinishThread == TRUE; // 종료를 위한 변수설정 후 3초간 대기
    
    DWORD dwExit=0;
    DWORD dwRetCode = WaitForSingleObject(_hThread, _nWaitMilSec);
    if(dwRetCode == WAIT_OBJECT_0) // 정상종료
    {
        _DbgStr(_T("WaitThreadEnd-정상 종료-%s-%X-%d"),_sMsg, _hThread, GetTickCount() );
    }
    else if(dwRetCode == WAIT_TIMEOUT) // 타임아웃
    {
        //::TerminateThread(_hThread, 0 );
        _DbgStr(_T("WaitThreadEnd-강제종료-%s-%X-%d"),_sMsg, _hThread, GetTickCount() );
    }    

    if( _bClose){  
        //AfxBeginThread()로 생성된 쓰레드는 핸들을 닫으면 안된다. CWinThread를 delete 해야한다.
        //m_bAutoDelete == TRUE 일때는 호출하면 안된다
        CloseHandle( _hThread );
    }

    _DbgStr(_T("WaitThreadEnd-끝-%s-%X-%d"),_sMsg, _hThread, GetTickCount() );
    return 1;
}

//AfxBeginThread() 로 시작한 쓰레드는 이함수로 종료해야 함
int WaitThreadEndW(CWinThread *_pWinThread, int _nWaitMilSec, LPCTSTR _sMsg)
{
    if( !_pWinThread ) return 0;
    HANDLE _hThread = _pWinThread->m_hThread;
    _DbgStr(_T("WaitThreadEnd(W)-시작-%s-%X-%d"),_sMsg, _hThread, GetTickCount() );
    //bFinishThread == TRUE; // 종료를 위한 변수설정 후 3초간 대기
    bool bAutoDel = _pWinThread->m_bAutoDelete;
    DWORD dwExit=0;
    DWORD dwRetCode = WaitThreadEndH( _pWinThread->m_hThread, _nWaitMilSec, _sMsg, false  );//핸들을 닫으면 안된다.
    
    //m_bAutoDelete = FALSE의 경우 스레드의 삭제
    if(bAutoDel==false)
    {
        GetExitCodeThread(_pWinThread->m_hThread, &dwExit);
        _DbgStr(_T("WaitThreadEnd(W)-쓰레드 클래스 삭제(code=%d)", dwExit));
        
        //if( _bClose) CloseHandle( _hThread );//하면 안됨클래스 해제시 자동 해제 된다.
        
        delete _pWinThread;  
    }    

    _DbgStr(_T("WaitThreadEnd(W)-끝-%s-%X-%d"),_sMsg, _hThread, GetTickCount() );
    return 1;
}
//===================================================================

 

[출처 : http://codens.info/678 / 2014.02.03 23:49]


출처: http://devkyu.tistory.com/506 [천천히, 빠르게. 개발자의 Repository]

 

 

 

 

 

=======================

=======================

=======================

 

 

 

출처: http://www.heeaecode.com/c-cpp-standard-thread/

다음은 스레드의 모범 사용 예 입니다.

 

#include ....

using namespace std;

//UINT는 unsigned int 를 줄여 UINT로 재정의되어 있으며 역시 가독성을 위해 MS가 제작
// WINAPI 는 c++ 호출 규약 __stdcall 로 재정의. 반대는 __cdecl 이 있음. 뭔지 몰라도 되고 스레드 함수 선언할 땐 아래 형태를 암기.

UINT WINAPI Thread(void* arg);

int main()
{
    // 핸들은 각 오브젝트? 창? 들의 고유 번호. 주소값 형태를 가지고 있음
    // console 창도 하나의 핸들을 가지고 있다.

    HANDLE hThread[2];

    // MS는 int 대신 가독성을 위해 대문자 INT를 권장함. INT랑 int는 동일하며 typedef로 정의됨. 
    INT numStart; 
    cout &lt;&lt; "시작할 값 입력하세요. ";

    cin &gt;&gt; numStart;
    cout &lt;&lt; "Thread Start" &lt;&lt; endl;

    // void 포인터로 스레드 함수에게 값을 넘겨줌.
    hThread[0] = (HANDLE) _beginthreadex(NULL, 0, Thread, &amp;numStart, 0, 0);

    // Thread 함수가 메인 함수 밖에서 선언 정의 되어있고, Thread() 를 쓰면 함수 호출이 되는데
    // () 를 쓰지 않으면 마치 배열 a[5]; 를 선언하고 a 만 쓰면 주소값을 얻을 수 있듯이 
    //함수도 마찬가지
    // _beginthreadex 함수한테 스레드 함수 주소를 넘겨주면 그 함수가 
    //스레드로 동작하게 되는 원리.
    // 스레드가 생성되고 핸들값을 받음.
    // 그 핸들은 스레드 고유 번호와 같다고 할 수 있으므로 아래 함수를 이용해 스레드 대기 가능.



    // 메인함수가 종료되면 스레드는 강제로 종료되므로 스레드가 끝날 때까지 기다리는 코드
    WaitForSingleObject(hThread[0], INFINITE);

    return 0;
}



UINT WINAPI Thread(void* arg)
{
    // void 포인터로 넘겨받기 때문에 자료형만 정해주면 됨.
    INT* pNumStart = (INT*) arg;

    while(*pNumStart &lt; 100)
    {

        cout &lt;&lt; (*pNumStart)++ &lt;&lt; endl;

        // *pNumStart++ = * 보다 ++ 연산자 우선순위가 높으므로 주소를 ++ 한 다음 값을 출력하므로 쓰레기 값이 출력됨.

        Sleep(100);
    }

    return 0;

}

 

 

 

 

=======================

=======================

=======================

 

 

 

 

출처: http://www.gpgstudy.com/forum/viewtopic.php?t=1390

이거야 원... 자주 질문할려니~ 죄송하네여~

전체글 글쓴이: 구재원 » 2003-01-10 01:23

초보란 이런건가요?? 흐...

제가 맵툴 만들고 있다는건 제 질문 하나라도 본 사람있으면 다 아실겁니다. ㅎㅎ

MFC+OpenGL으로 하고 있는데... 렌더링 루프를 한번 쓰레드로 걸어볼까 했습니다.

OnDraw를 타이머로 1000/10초마다 불러 썼지만.. 그러면 메시지 우선순위땜시리 

괜히 다른것들이 느려져 렌더링 루프를 쓰레드로 처리하려고 하는데~

잘 안되네요... 화면 자체가 안나오더군요... 

CXXXView 클래스의 OnCreate에는 OpenGL을 초기화하는 코드와

쓰레드 작성을 합니다. 그리고 쓰레드는 분명 돌아가지만.. 화면에는

원하는 색으로 칠해지지 않고 창의 잔상만 남습니다... 그래서 OpenGL의

초기화 코드 역시 쓰레드 안으로 넣고 돌렸지만 이번엔 실행자체가 안되더군요.

코드: 모두 선택

 

// 쓰레드 부분
UINT RenderLoop(LVOID pParam) {
    OpenGLInfo OGInfo = (OpenGLInfo * ) pParam;

    theSimpleOpenGL.Init((CView * ) OGInfo - > pView); // OpenGL 초기화 함수

    while (OGInfo - > bFlag) {
        theSimpleOpenGL.Prepare(); // 그리기전 준비한다. 화면 지우고 행렬을 모델뷰로 변환한다. 그리고 행렬 초기화

        pTheSimpleCamera.Update(pTheSimpleTimer - > GetElapsedTime());

        theSimpleField.Render(); // 지형을 렌더링 한다.

        theSimpleOpenGL.Flip(); // 화면버퍼를 스왑한다.
    }

    delete OGInfo;
    delete pTheSimpleCamera;
    delete pTheSimpleTimer;
}

 

음... 이것이 쓰레드 부분인데요.. 분명 저 쓰레드는 제대로 돌아가는데(카메라 계산이 다됨).. 그 기능(그리지를 못할뿐)을 못하더군요.

제 생각은 아무래도 DC랑 관계가 있을 것 같은데... 주 프로세서와 쓰레드에서 DC를 어떻게 해야 하는지... 플밍 초보로써~ 궁금하네요~

그럼 좋은 가르침 기다리겠습니다.

상위

 

강성진전체글: 63가입일: 2003-01-03 21:03

도움이 될지는 모르지만..

전체글 글쓴이: 강성진 » 2003-01-10 02:09

질문하신 쓰레드 관련은 모르겠고, (OpenGL 에 쓰레드 관련 문답이 여기 포럼에도 있습니다.http://www.gpgstudy.com/forum/viewtopic ... 7%B9%B5%E5)

MFC 용 툴만들때 고쳐썻던 놈을 참고하시라고 올립니다. 예전에 잘 썼던건데, 맞나 안맞나 몰겠군요.

코드: 모두 선택

 

int CFMEApp::Run() {

        // TODO: Add your specialized code here and/or call the base class
        // Load keyboard accelerators
        HACCEL hAccel = LoadAccelerators(NULL, MAKEINTRESOURCE(IDR_MAINFRAME));

        CMainFrame * pFrame = (CMainFrame * ) AfxGetMainWnd();
        // Now we're ready to recieve and process Windows messages.
        BOOL bGotMsg;
        MSG msg;
        msg.message = WM_NULL;
        PeekMessage( & msg, NULL, 0 U, 0 U, PM_NOREMOVE);

        while (WM_QUIT != msg.message) {
            // Use PeekMessage() if the app is active, so we can use idle time to
            // render the scene. Else, use GetMessage() to avoid eating CPU time.
            if (g_bActive) // 활성화 여부임다.
                bGotMsg = PeekMessage( & msg, NULL, 0 U, 0 U, PM_REMOVE);
            else
                bGotMsg = GetMessage( & msg, NULL, 0 U, 0 U);

            if (bGotMsg) {
                if (IsIdleMessage( & msg)) {
                    OnIdle(0);
                }

                // Translate and dispatch the message
                if (0 == TranslateAccelerator(pFrame - > GetSafeHwnd(), hAccel, & msg)) {
                    TranslateMessage( & msg);
                    DispatchMessage( & msg);
                }
            } else {
                // 요때 메인루프나 화면갱신 같은거 해주면 됩니다. 
                if (g_bActive) MainLoop();

            }
        }

        ExitInstance();

        return (INT) msg.wParam;

 

상위

구재원

우선 감사드리고 싶습니다.

전체글 글쓴이: 구재원 » 2003-01-10 03:00

답변 주신거 감사합니다. ㅠ.ㅠ

아직 해보지는 못하겠네요... 여기는 피씨방~ 밤샘 워크3를 즐기고 있습니다.

정말 제 궁금증을 풀어주셔서 감사합니다~

전 검색해도 잘 안나왔는데~ ㅠ.ㅠ (왜 난 검색이 안되징...)

답변이 있었군요... ㅎㅎㅎ.. 우선 잠자고 낼 아침에 해봐야 겠네요.

감사합니다.

상위

구재원

흐미~ 이런 일이...

전체글 글쓴이: 구재원 » 2003-01-10 05:36

헉.. 5시에 집에 도착하고~ 위의 CXXXWin의 가상함수인 Run()을 구현해봤습니다..

처음에 화면의 속도는 괜찮게 나오지만... 마우스가 MFC 컨트롤로 가면 마우스도

각기(Poping)~ 화면도 각기(Poping)~ 어째 이런일이~

음... 어케된걸까요?? 분명히 저방식을 그대로 썼는데...

상위

구재원

헐... 이런경우도 있나...

전체글 글쓴이: 구재원 » 2003-01-10 07:09

이상하게도

렌더링 양이 많아질수록 끊김이 덜합니다...

아예 끊김이 사라진다고 해야죠...

이런경우도 있는지...

렌더링양이 조금이면 MFC 컨트롤에 마우스만 갖다대면 끊기더니만

렌더링양이 많아지면 갖다대도 전혀 무반응입니다. 즉 끊기지가 않죠...

음... 어째서리...

상위

 

Gamza전체글: 610가입일: 2001-10-11 09:00연락처: 

Gamza 님의 연락처

 

전체글 글쓴이: Gamza » 2003-01-10 08:46

Run을 오버라이딩하지 마시고 OnIdle을 사용하심이....

HOME: http://www.Gamza.net

상위

구재원

답변 주셔서 감사합니다.

전체글 글쓴이: 구재원 » 2003-01-10 09:15

위에 감자님이 OnIdle을 오버라이딩으로 하라고 하시는데...

간단한 예를 부탁드립니다.

저는 초보...

다시한번 답변 주신 감자님께 감사를 드립니다. ^-^

상위

 

박필관전체글: 27가입일: 2002-08-23 10:59

요기로..

전체글 글쓴이: 박필관 » 2003-01-10 09:20

http://www.spirit3d.net/ 의 article 35번을 참고하세요..

//---후휘휠---//

상위

구재원

우훨훨~ 감사합니다.

전체글 글쓴이: 구재원 » 2003-01-10 09:27

전 아무래도 검색 능력을 키워야 겠습니다.~

자주 가는 사이트인데도.. 지금에서야 보다닝~ ㅋㅋㅋ...

감사합니다.

감사의 의미로... 스샷 한장 현재 맹글고 있는 맵툴

상위

 

Gamza전체글: 610가입일: 2001-10-11 09:00연락처: 

Gamza 님의 연락처

 

전체글 글쓴이: Gamza » 2003-01-10 09:39

써본지가 워낙 오래되나서....VC++ 4.1때 써보고는...한번도...ㅡ.ㅡ;;

Run과 마찬가지로 사용하시면 됩니다.

CWinApp::OnIdle 
virtual BOOL OnIdle( LONG lCount );

Return Value
0이아닌값을 반환하면 메세지큐가 비어있을동안 계속해서 OnIdle을 호출해줍니다.
게임이라면 그냥 TRUE을 반환하면 되겠죠.

Parameters
lCount
OnIdle이 호출될때마다 증가되는 카운터값.
메세지를 처리하다가 메세지큐가 empty가되면 0으로 초기화된다.
결국 아무런 메세지없이 OnIdle이 연속 호출된 횟수를 카운팅하는셈.

....

참고로 MFC의 Run은 이렇게 OnIdle을 호출해줍니다.

코드: 모두 선택

// VC++6.0의 MFC소스에서 발췌 // main running routine until thread exits int CWinThread::Run() { ASSERT_VALID(this); // for tracking the idle time state BOOL bIdle = TRUE; LONG lIdleCount = 0; // acquire and dispatch messages until a WM_QUIT message is received. for (;;) { // phase1: check to see if we can do idle work while (bIdle && !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)) { // call OnIdle while in bIdle state if (!OnIdle(lIdleCount++)) bIdle = FALSE; // assume "no idle" state } // phase2: pump messages while available do { // pump message, but quit on WM_QUIT if (!PumpMessage()) return ExitInstance(); // reset "no idle" state after pumping "normal" message if (IsIdleMessage(&m_msgCur)) { bIdle = TRUE; lIdleCount = 0; } } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)); } ASSERT(FALSE); // not reachable }

HOME: http://www.Gamza.net

상위

강성진

아고, 쑥쓰러버라..

전체글 글쓴이: 강성진 » 2003-01-10 11:29

이거 틀린거 올려놨더니 영 부끄럽고, 죄송하네요.. -.,-

예전에 저렇게도 해보고, Onidle 도 고쳐서 해보고 했는데, 저게 더 나아서 했던거 같은데.. 안된다니 저도 어리둥절합니당.

여튼 결론은 메시지 있으면 그거 처리하고, 없을땐 우리꺼 돌리자 입니다. 헤헤,
(어물쩡 넘어갑니다..) 만드시는거 잘 만드시길.

상위

macwin전체글: 127가입일: 2001-08-04 09:00연락처: 

macwin 님의 연락처

헐..

전체글 글쓴이: macwin » 2003-01-10 17:37

전 타이머로 해도 잘되던데..쿨럭..

(뭔가 문제가 있을지도 ㅠ.ㅠ)

스스슥...

EMAIL : macwin at hitel.net
http://blog.naver.com/macwin

상위

구재원

답변주신 모든분들께 감사드립니다.

전체글 글쓴이: 구재원 » 2003-01-10 20:22

OnIdle 메시지처리에서 하니 속도도 괜찮게 나오고

더이상 끊김이 없더군요... 그런데... OnIdle을 직접 오버라이드해서 

쓰고 있는데요. 인터페이스 갱신이 안되는 군요..

조사해보니 CFrameWnd::OnIdleUpdateCmdUI 를 OnIdle에서 발생시키는것 같은데

저 같은경우는 달랑 화면 갱신만 있어 아래와 같이 추가했지만

AfxGetMainWnd()->OnIdleUpdateCmdUI();

CFrameWnd의 protected 로 되어 있어 사용할수가 앖다고 나오는 군요.

기존의 OnIdle에서는 어떻게 처리한 것일까요??

그럼 모두들 즐거운 하루 되세요.

상위

 

Gamza전체글: 610가입일: 2001-10-11 09:00연락처: 

Gamza 님의 연락처

 

전체글 글쓴이: Gamza » 2003-01-11 06:53

MFC에선 오버라이딩을 할때....특별한일 없으면 원래 함수를 호출해주는게 좋습니다.

뭔가 해주어야 하는데 안해주고 넘어가게 될지도 모르므로...

아래처럼....

코드: 모두 선택

BOOL MyMFCApp::OnIdle(LONG lCount) { CWinApp::OnIdle(lCount); .... return TRUE; }

HOME: http://www.Gamza.net

상위

구재원

헉~ 그렇군요...

전체글 글쓴이: 구재원 » 2003-01-12 04:09

아~ 감자님이 말씀하신대로 하니~

별문제없이 해결되었습니다.. 

정말 감사합니다. ^-^

그럼 모두들 새해 복 많이 받으세요.

상위

 

 

 

=======================

=======================

=======================

 

 

 

 

출처: http://www.gpgstudy.com/forum/viewtopic.php?t=1165&highlight=%BE%B2%B7%B9%B5%E5

 

안녕들 하십니까...

쓰레드를 하나 생성시켜서 텍스쳐를 로딩하고 그것을 메인쓰레드에서 렌더링하는 프로그램을 만들었

는데, (그렇게 한 이유는 디스크 I/O 가 일어날때 프레임 드랍이 생기는 것을 최대한 막아보려고...

 상당히 효과가 있습디다) 문제는 무엇인가 하면... 쓰레드에서 생성된 텍스쳐는 메인쓰레드에서

 사용이 불가능하다... 하는 겁니다. 정확히 말하자면 텍스쳐가 생성이 안됩니다. GetLastError의 값은

170 The requested resource is in use. ERROR_BUSY 

이런 값이 뜹니다.

즉...


unsigned int iTexture; // 텍스쳐 번호

UINT ThreadFunc(LPVOID p) 
{

...

< 텍스쳐를 로딩하고 iTexture 에 그 id를 넣는다 >
< 다른 GL 함수는 사용하지 않음 >

----> iTexture 값이 0 으로 되어버린다는... 

 
...

}



void RenderScene()
{
...

glBindTexture(GL_TEXTURE_2D, iTexture);
// 이 함수가 효과가 없음
// iTexture 의 값이 0 이기 때문에 당연히... 

 

...
}


문제가 뭔지는 알겠는데 해결방법을 생각하려니 난감하기도 하고...
이런 문제로 고민해보신 분들의 의견을 구합니다...

제가 멀티스레드는 모르지만
스레드를 만들떄

beginthread를 사용하셧는지 아니면
CreateThread를 사용하셧는지.....


CreateThread는 뭐뭐가 안된다고 beginthread를 쓰라고 하더군요....

잡담이엇습니다 

류광전체글: 3742가입일: 2001-07-25 09:00사는 곳: GPGstudy연락처: 

류광 님의 연락처

 

전체글 글쓴이: 류광 » 2002-11-23 17:53

그냥 짐작인데 그래픽 카드가 장면을 렌더링하는 도중에 텍스처를 갱신하려고 한 게 아닐까 싶네요. 

동기화를 어떻게 처리하셨는지 궁금합니다...

그리고 CreateThread와 beginthread 차이는 스레드 안에서 Win32 API 를 호출하느냐 않느냐의 

차이라고 알고 있는데 역시 자세하게는 잘 모르겠구요 -.-

상위

박세원

CreateThread와 beginthreadex의 차이.

전체글 글쓴이: 박세원 » 2002-11-23 23:14

c runtime library를 사용할 수 있느냐 없느냐의 차이라는군요.
CreateThread로 생성한 스레드 안에서 c runtime library를 사용하면
메모리가 줄줄 샌다는 소문이.... 있던가? --;; 합니다.
beginthreadex는 c runtime library의 초기화 루틴이 들어가 있다는군요.

참고로 beginthread는 쓰레기라는.. 반드시 beginthreadex를 써야 한다는...

상위

362496전체글: 91가입일: 2001-11-09 09:00

문제의 원인은...

전체글 글쓴이: 362496 » 2002-11-24 21:23

일단 모든 문제의 발단은 "렌더링하면서 텍스트를 갱신한다"는데 있는 것 같습니다.

물론 현재 바인드되어 있는 텍스트를 갱신하지는 않습니다. 있지도 않는 텍스트를 바인딩하게 

코딩할 리가 있겠습니까...

그리고 beginthread 냐 CreateThread 냐는 별로 중요한 문제처럼 보이지는 않는데요...

 텍스쳐로 사용할 데이타는 잘 읽어지거든요...

렌더링을 하면서 백그라운드로 텍스쳐를 읽어오고, 렌더링하는 쓰레드에서는 텍스쳐가 

읽어졌는지를 확인해서 읽어져 있는 텍스트만 바인딩해서 렌더링에 활용하는데... 

어디서 문제가 생기는지...

상위

soaringhs전체글: 230가입일: 2001-07-30 09:00

 

전체글 글쓴이: soaringhs » 2002-11-24 22:29

GL에 쓰레드 지원을 위한 기능이 없는것이 문제가 아닐까요?
여러 쓰레드에서 한 자원을 사용 할려면 우선 선점한 쓰래드에서 락을 걸어 다른 쓰레드의 진입?을 

막는데 GL에선 과정이 따로 없다는것이 문제 일것 같군요. 에러에 BUSY가 나온거 봐서는 관련 

익스텐션이 있을것 같기도 합니다.

없다면 IO쓰레드는 이미지만 메인 메모리에 올려 두었다가 GL쓰레드에서 텍스쳐 로딩/바인딩해서 

사용하는것이 좋을것 같네요.

MS GL 소프트웨어버전은 멀티스레드를 지원 안한다고 나와있긴 한데... 가속기는 정확히 모르겠습니다.

상위

362496전체글: 91가입일: 2001-11-09 09:00

예... 충분히 가능성이 있습니다...

전체글 글쓴이: 362496 » 2002-11-24 23:30

MSDN을 보면... 모든 GL 함수들은 "rendering context(RC)"에다가 렌더링을 하게 되고 이 RC들은

 "calling thread"와 연관되어 있는 것으로 나와 있거든요...

그래서... makefile 님 의견대로 I/O 쓰레드에서 로딩만 하고, 렌더링하면서 텍스쳐로 로딩/바인딩

하게도 해봤습니다. (안해봤겠습니까... 

 )

그렇게 하면 텍스쳐가 제대로 나오기는 하지만, 프레임 드랍이 생깁니다. 싱글쓰레드로 했을 때보다는

 훨씬 낫지만, 그래도 텍스쳐를 로딩/바인딩 하는 과정이 시간이 상당히 많이 걸리게 됩니다. 아마도 

텍스쳐가 커서 그럴겁니다... 2048x2048 ... 

 

생각할수록 괴물을 만들고 있다는 느낌입니다...

^^;

상위

류광전체글: 3742가입일: 2001-07-25 09:00사는 곳: GPGstudy연락처: 

류광 님의 연락처

uh-oh

전체글 글쓴이: 류광 » 2002-11-25 01:23

에구 362496 님 그동안 시도해 본 것, 고려해 본 것도 알려주셔야 토론이 불필요하게 뒤로 감기는

 일이 없을 것 같네요...

텍스트를 바인딩하게 코딩할 리가 있겠습니까... 
....

(안해봤겠습니까... )

다른 분들이야 알 수 없죠 ^^;;

상위

362496전체글: 91가입일: 2001-11-09 09:00

하하...

전체글 글쓴이: 362496 » 2002-11-25 08:30

그렇게 되나요...


자... 다시, 이해를 돕기 위해 사건의 내막을 다시 정리합니다...


지금 작업하는 것은 flight sim. 류의 프로그램이고, 문제가 되는 것은 지형을 로딩하는 부분입니다. 

지형 데이타가 워낙 방대하기 때문에 여러개의 구역으로 쪼갰고요... 시점이 이동함에 따라서 

로딩해야 될 구역과 그렇지 않은 구역을 검사합니다. 그래서 필요한 구역을 로딩하는 것이지요. 

이 때 로딩할 때의 I/O 타임 동안 프레임 드랍이 생기는 것을 막기 위해 "검사하고 로딩하는 I/O 과정"을 

별도의 쓰레드를 생성시켜 백그라운드로 돌리게 하는 것입니다. 이렇게요...


(0) 처음 로직...

void ThreadFunc() {
while(bRunning) {
CheckSectors(); // 검사하고 로딩하는 부분...
}
}


void Init() {
...
CreateThread(
...
ThreadFunc,
...
);
...

}

void RenderScene() {
....
RenderSectors(); // 로드된 구역들을 렌더링하는 부분
....

glutSwapBuffers();
}


뭐 대충 이런 식의 로직입니다. 이렇게 하면 I/O가 일어나는 동안에도 프레임 드랍이 거의

 안생기는데, 텍스쳐가 안나온다는거지요...


(1) 싱글쓰레드 버전
그래서... 쓰레드를 돌리지 않고...

void Init() {
...
/*
CreateThread(
...
ThreadFunc,
...
);
*/ // 이부분을 막아버리고
...

}

void RenderScene() {
....
CheckSectors(); // 검사하고 로딩하는 부분...
RenderSectors(); // 로드된 구역들을 렌더링하는 부분
....

glutSwapBuffers();
}

이렇게 싱글쓰레드로 하면, 텍스쳐는 잘 나오지만 로딩하는 순간 프레임 드랍이 심각합니다.



(2) 텍스쳐 바인딩 부분을 떼어내면...
그래서 생각해낸 것이, CheckSectors() 함수 속에 들어있던 

glBindTexture(....);
gluBUildMipmap(....);

이런 코드들을 RenderSectors() 로 옮기고, 

로딩이 다 되면 CheckSectors() 함수에서 플래그를 true 로 만든 다음에...
RenderSectors() 함수에서는 이 플래그만 검사해서 true 면 아까 떼어 온

glBindTexture(....);
gluBUildMipmap(....);

을 실행하게 해놨습니다. 이렇게 하면 텍스쳐도 잘나오고 동작도 잘하는데, 역시나 프레임 드랍이 

전혀 안생기는 것은 아니라서 쓸만한 결과를 보여주지는 못합니다.


------------
현재 진도는 여기까지입니다. 좀더 고민해보고, 경과를 올려드리지요...

상위

류광전체글: 3742가입일: 2001-07-25 09:00사는 곳: GPGstudy연락처: 

류광 님의 연락처

 

전체글 글쓴이: 류광 » 2002-11-25 16:31

여기서 잠깐 나름대로 정리를 해보면요...

-. 프레임 드랍을 막는다는 것의 구체적인 의미는 텍스처 갱신의 부담을 모든 프레임들로 분산시키는 

것을 말한다( = 스레드를 사용한다고 해서 파일 I/O, 텍스처 업로드 자체가 빨라지는 것은 아니다).

-. 주된 병목은 순서대로 파일 I/O, 밉맵 생성, 텍스처 업로드이다.(물론 구체적인 프로파일링이

 필요하겠지만요)

제시하신 0 번은 명시적이고 세밀한 동기화가 없다면 당연히 안 되리라고 보구요.

1 번은 갱신 부담을 분산시키지 못하는 것이므로 역시 비현실적.

현재 2 번이 가장 바람직하나 밉맵 생성을 분산시키지는 못하는 것 같습니다. 텍스처 업로드는 그래픽

 카드와의 상호작용이니 분산이 불가능하구요... (그 상호작용에는 렌더링도 포함되니 어쩔 

수 없는 문제입니다).

밉맵 생성은 메모리 할당, 평균 연산 등등 상당히 비싼 과정이라고 알고 있는데, 그걸 스레드에 넣는

 쪽으로 해볼만하지 않을까요? 

gluBuildMipmap이 스레드 문제에 걸린다면, 파일에서 읽은 데이터로부터 직접 밉맵들을 생성하고, 

업로드에서 glTexImage2D 의 두 번째 인수를 통해서 밉맵들을 직접 올리는 것도 좋을 듯 합니다. 

이 때 각 밉맵을 한 꺼번에 모두 올리지 말고 몇 프레임에 걸쳐서 올릴 수도 있겠구요.

더 나아가서, 스레드를 쓰지 않고 텍스처 갱신 전과정을 여러 프레임들로 직접 분산시키는 것도 

해볼만할 것이라고 봅니다. 스레드가 편하긴 하겠지만, 처리 과정을 직접 시분할한다면 렌더링과 

텍스처 처리 작업에 투여되는 CPU 시간을 좀 더 명시적으로 제어할 수 있는 장점이 생길 것 같습니다.

 (A* 길찾기에도 그런 예들이 있구요...)

또 더 나아가서, 비행기의 최근 n 프레임(또는 n 초) 간의 평균 방향을 얻고 다음 섹터 또는 다음 다음 

섹터도 미리 읽어두는 식의 휴리스틱적인 접근도 필요할 것이라고 봅니다...

상위

362496전체글: 91가입일: 2001-11-09 09:00

오늘의 진행(?) 상황

전체글 글쓴이: 362496 » 2002-11-25 17:32

일단 MSDN 이랑 인터넷을 뒤져 알아낸 것은...

GL 함수의 대상이 되는 "rendering context"는 반드시 하나의 쓰레드에서만 "make current" 되어야 

한다는 것입니다. 즉, 렌더링하고 있는 RC를 놔두고 백그라운드로는 다른 어떤 GL 함수도 사용할 수

 없다(!) 

 는...


뭐 그런 것입니다. 즉 "동기화의 대상"이 I/O나 로딩할 파일, 텍스쳐로 올릴 비트맵 등등 뿐만 아니라 

RC 그 자체도 해당된다는 얘기지요...

대대적인 "갈아엎음" 이 불가피할 것으로 보입니다... 안타깝지만... 


-------------------------


그리고... 광님의 의견...

-. 프레임 드랍을 막는다는 것의 구체적인 의미는 텍스처 갱신의 부담을 모든 프레임들로 분산시키는

 것을 말한다( = 스레드를 사용한다고 해서 파일 I/O, 텍스처 업로드 자체가 빨라지는 것은 아니다). 

-----> 맞습니다. 하지만 파일I/O 자체가 CPU를 먹는다기보다는 I/O가 이루어질 동안 CPU가 놀면서 

기다리는 시간이 더 많기 때문에, 싱글쓰레드로 순차적인 작업이 이루어질 때보다 시간은 확실히

 적게 걸립니다. 게다가 타겟머신을 멀티CPU 기계로 잡고 있기 때문에 더더욱... 

(빨라지지는 않지만 시간은 적게 걸린다...는 말도안되는 결과가...^^;)



-. 주된 병목은 순서대로 파일 I/O, 밉맵 생성, 텍스처 업로드이다.(물론 구체적인 프로파일링이 

필요하겠지만요) 

----> 맞습니다. 저도 구체적인 프로파일링은 안해봤지만, 대충 몇초나 걸리나 세어보면 이 순서대로

 되는 것 같습니다.





제시하신 0 번은 명시적이고 세밀한 동기화가 없다면 당연히 안 되리라고 보구요. 

----> 말씀처럼 명시적이고 세밀한 동기화 (섹터단위의 Lock, Unlock)을 다 해놨기 때문에 "쫑나는" 

사태는 안납니다. 유일한 문제는 위에서 써놓은 것처럼 GL 함수가 안먹는다는... RC 자체에 대한

 명시적이고 세밀한 동기화가 역시 들어가야 된다...는 얘기지요...



1 번은 갱신 부담을 분산시키지 못하는 것이므로 역시 비현실적. 

----> 맞습니다. 1번으로 코드를 완성하려 했다면 처음부터 게시판에 썼겠습니까... ^^;




현재 2 번이 가장 바람직하나 밉맵 생성을 분산시키지는 못하는 것 같습니다. 텍스처 업로드는 그래픽 

카드와의 상호작용이니 분산이 불가능하구요... (그 상호작용에는 렌더링도 포함되니 어쩔 수 없는 

문제입니다). 

----> 궁극적으로 "텍스쳐 업로드"를 백그라운드로 빼낼 수만 있다면 모든 문제가 해결되는 셈입니다. 

관련 FAQ들을 읽어봐도, 쓰레드들 간의 "업로드된 텍스쳐의 공유"에 관한 문제들이 나오는데, 이것을 

구현하는데 문제가 많은 모양이었습니다... 결국 원하는 것은 이건데... 

 





밉맵 생성은 메모리 할당, 평균 연산 등등 상당히 비싼 과정이라고 알고 있는데, 그걸 스레드에 넣는 

쪽으로 해볼만하지 않을까요? 

gluBuildMipmap이 스레드 문제에 걸린다면, 파일에서 읽은 데이터로부터 직접 밉맵들을 생성하고, 

업로드에서 glTexImage2D 의 두 번째 인수를 통해서 밉맵들을 직접 올리는 것도 좋을 듯 합니다. 

이 때 각 밉맵을 한 꺼번에 모두 올리지 말고 몇 프레임에 걸쳐서 올릴 수도 있겠구요. 

----> 그렇다면 밉맵을 생성시키지 않고 싱글 LOD로 텍스쳐를 만든다면 시간이 단축되겠습니까...? 

한번 해보고 결과를 알려드리도록 하지요... 저도 궁금합니다...







더 나아가서, 스레드를 쓰지 않고 텍스처 갱신 전과정을 여러 프레임들로 직접 분산시키는 것도 

해볼만할 것이라고 봅니다. 스레드가 편하긴 하겠지만, 처리 과정을 직접 시분할한다면 렌더링과 

텍스처 처리 작업에 투여되는 CPU 시간을 좀 더 명시적으로 제어할 수 있는 장점이 생길 것 같습니다.

 (A* 길찾기에도 그런 예들이 있구요...) 

또 더 나아가서, 비행기의 최근 n 프레임(또는 n 초) 간의 평균 방향을 얻고 다음 섹터 또는 다음 다음 

섹터도 미리 읽어두는 식의 휴리스틱적인 접근도 필요할 것이라고 봅니다...

----> 현재는, 비행기가 섹터의 경계를 넘어갈 때 주위 섹터들을 다시 검색해서 필요한 섹터들을 

로딩하는 방식이라, 한순간에 I/O가 몰리면서 프레임이 드랍되는 현상이 발생합니다. 이 과정을

 분산하면 상당히 도움이 많이 되겠군요...





--------------------

관심 고맙습니다...

상위

sumis0전체글: 13가입일: 2002-08-20 14:07

제가 아는 부분에 대해 답글을 달면..

전체글 글쓴이: sumis0 » 2002-11-25 18:29

님의 글을 보니 제가 하는 것과 거의 똑같은 작업을 하시는 것 같은데요..

위에 언급한 내용에 대해 제가 아는 바에 대해 답글을 달겠습니다. 


1. 밉맵 생성을 다른 쓰레드로 분리하기
"그렇다면 밉맵을 생성시키지 않고 싱글 LOD로 텍스쳐를 만든다면 시간이 단축되겠습니까...? 

한번 해보고 결과를 알려드리도록 하지요... 저도 궁금합니다... 
"
이런 의견을 말씀하셨는데요, 제 생각에는 싱글 LOD 로 텍스처를 만드는 것보다
류광님의 의견대로 하시는게 좀 더 도움이 되리라 생각됩니다. 
그 이유는 밉맵의 사용하는 이점과 더불어, 
밉맵 레벨에서 가장 자세한 레벨의 텍스처의 크기의 약 3배가 되기 때문입니다. ( (1/4)/(1-1/4) )
즉 가장 자세한 레벨의 텍스처를 올리는것이 그 이전에 비해 크다고 생각됩니다. 
따라서, 류광님의 의견대로 gltexImage2 두 번째 인수를 이용하여 필요한 레벨에 해당하는 텍스처를

 업로딩하는 것이 효율적이라는 생각이 듭니다. 
특히 다음과 같은 키워드가 유용하리라 생각됩니다. 
(GL_TEXTURE_MAX_LEVEL_EXT,GL_TEXTURE_BASE_LEVEL_EXT)

2. 지형 및 텍스처 분산 로딩(?)
(섹터라는 말을 제가 정확히 이해하지 못했지만.. )
지형 및 텍스처 자료를 로딩할때 필요한 부분만 읽게하면 
약간 문제가 발생할 수 있습니다. 
제가 경험한 바에 따르면, 저는 지형 및 텍스처 자료를 동서 , 남북 방향에 
평행한 4각형 셀로 나누어 저장하고 이를 로딩하였는데요
비행기가 동서 나 남북 방향에 평행하게 날게 되면.. 
셀 경계에서 한꺼번에 많은 지형 및 텍스처 자료를 읽어 
오는 현상이 발생합니다. 
지형 및 텍스처 자료를 예측하여 로딩한다고 해도 비슷한 현상이 
발생할 수 있기 때문에, 
지형 및 텍스처 자료를 예측해서 로딩한다는 개념보다는 류광님이 말씀하신대로
한 프레임의 일정량만 로딩되게 할 수 있도록 하는 분산로딩의 접근이..
프레임 드라핑(?) 현상을 보다 효과적으로 
막을 수 있는 방안이 아닐까 생각됩니다. 

써넣고 보니.. 그다지 새로운 내용이 없군요.. 
362496 님께 부탁 말씀드리겠습니다. 
멀티 쓰레딩에서 텍스처 공유에 관련된 문서나.. URL을 알려주시면 
감사하겠습니다. 이부분이 정말 저한테 시급한 것인데.. 
OPEN GL의 한계라고 생각하고 넘어간 부분이었거든요.
그러면 많은 도움이 되셨기 바랍니다.

상위

362496전체글: 91가입일: 2001-11-09 09:00

하하...

전체글 글쓴이: 362496 » 2002-11-25 19:08

동지가 한분 더 늘었군요...


sumis0 님의 의견에 대해...
----------------------------

이런 의견을 말씀하셨는데요, 제 생각에는 싱글 LOD 로 텍스처를 만드는 것보다 류광님의 의견대로 

하시는게 좀 더 도움이 되리라 생각됩니다. 

----> 물론입니다. 밉맵을 사용하면 안할 때보다 좋은 점이 많다는 것은 저도 잘 압니다. 제가 해보고 

싶은 것은 "밉맵을 생성하는 과정이 얼마나 CPU를 먹는가"를 측정(!)하는 것입니다. 그것이 측정이

 되어야 어떻게 분산시킬지 판단이 서거든요... 밉맵 없이 프로그램을 개발하려는 것은 아니랍니다...
지금 작업해놓은 코드에서 변수값 한개만 바꾸면 밉맵 없이 텍스쳐가 로딩되게 해놨기 때문에 간단히 

측정해볼 수 있거든요...







섹터라는 말을 제가 정확히 이해하지 못했지만.. 
...
저는 지형 및 텍스처 자료를 동서 , 남북 방향에 
평행한 4각형 셀로 나누어 저장하고 이를 로딩하였는데요 

---> 제가 사용했던 구역, 또는 "섹터"의 개념은, 님께서 표현했던 "셀"과 정확히 일치합니다. "sector"의 

사전적 의미 "부채꼴"과는 별 상관없이 그냥 "구역"의 의미로 사용되었습니다. SF소설 따위에서 

그런 예가 있는데요... 어감이 나쁘지 않아서 그냥 썼습니다. 혼란을 끼쳐드렸다면 죄송하고요...




멀티 쓰레딩에서 텍스처 공유에 관련된 문서나.. URL을 알려주시면 
감사하겠습니다. 이부분이 정말 저한테 시급한 것인데.. 
OPEN GL의 한계라고 생각하고 넘어간 부분이었거든요

---> 여기저기 들쑤시고 다녀서 URL 기록해놓는 것도 잊어버렸습니다. 의외로 자료가 많지 않습니다.

 어차피 몇번은 더 찾아야 할테니 읽어볼만한 자료는 모아서 공유하도록 하지요...




------------------
관심 고맙습니다...

상위

soaringhs전체글: 230가입일: 2001-07-30 09:00

 

전체글 글쓴이: soaringhs » 2002-11-25 19:57

우선 texture bind만 얼마나 드롭이 생기는지 테스트 해보셔야 할것 같네요. 

예전에 voodoo,riva tnt쓸때 테스트 해본것으로는 256*256*32bit 텍스쳐 한장을 bind 자체만으로도

(텍스쳐를 비디오메모리에 올릴때) 상당한 부하가 걸렸습니다. 프래임이 1/5로 줄었던가 그랬죠. 

요즘은 AGP4/8이니 조금 빨라졌겠지만 그래도 부하는 클 겁니다.

크기가 줄면 줄수록 프래임 드롭이 적어 질테니 적정수준의 크기부터 구해봐야겠죠. 하지만 

너무 작아지면 어쩔수 없이 프래임 드롭을 감수할수 밖에 없겠죠. 



그리고 밉맵생성은 따로 구현(아니면 GL클론 MESA의 것을...)해서 GL쓰레드에서 직접

 바인딩하시는것이 좋겠습니다.

이건 해당사항이 아닌것 같지만 만약 비디오 메모리에 텍스쳐에 할당된양보다 많은 텍스쳐를

 사용하신다면 계기 및 GUI등 교체되지 않고 매 프래임 계속 출력되는 텍스쳐들의 우선순위를

 높혀서 swap안되게 만들어 swap양을 줄이면 이득이 있을겁니다.

코드: 모두 선택

IO_n_MipMap_Thread() { 메인메모리에 밉맵 텍스쳐까지 생성 } GL_Render_Thread() { 렌더링.. SwapBuffer() if(새 텍스쳐?) { 텍스쳐 바인딩.. // 벤치결과 적정수준의 양만.. 넘치는건 다음 프래임에... glFlush() } 프래임 리미터 } Physics_Thread() { // 고정 프래임 }

제가 아는 한도내에선 이정도 밖에 안나오는군요. 

상위

kh2ya전체글: 77가입일: 2002-07-22 21:59

 

전체글 글쓴이: kh2ya » 2006-06-06 01:50

저도 이 문제점 지금 봉착되었는데
정보 좀 얻을 수 있을까요?

URL 이라든가 좀...

구글에서 검색해 봐도 영어가 딸려서 잘 모르겠더라구요.

상위

362496전체글: 91가입일: 2001-11-09 09:00

문제는 RC 입니다.

전체글 글쓴이: 362496 » 2006-06-07 09:18

MS의 OpenGL 구현에서는, 모든 GL 함수의 호출은 RC (rendering context)를 기반으로 이루어집니다. 

D3D 에서의 렌더링 관련 함수 호출이 IDirect3DDevice... 인터페이스를 기반으로 이루어지는 것과 

비슷합니다. 문제는 RC 가 쓰레드간 공유가 되지 않는 데 있습니다. 그래서 쓰레드마다 RC를 만들고 

이들이 리스트 공간(텍스쳐 번호, 디스플레이 리스트 등)을 공유할 수 있게 wglShareLists 함수를

 불러줍니다.
그러고 나서는 위에 있는 것처럼 (그새 세월이 좀 지났군요) 작업합니다.

그리고, 각 쓰레드(와 그에 따른 RC)간 동기화를 맞춰주기 위해서 각 쓰레드마다 glBindTexture 양끝에

 크리티컬 섹션을 넣어서 동시에 바인드가 되지 않게 합니다. 물론 갱신 중인 텍스쳐를 렌더링용으로 

사용하지 말아야 합니다. 이건 텍스쳐 갱신 쓰레드를 잘 구성하면 별도의 동기화개체 없이도 

간단히 해결됩니다.

대충 이런 식입니다. 생각나는 대로 적어서 제대로 동작할 지는 모르겠는데, 전체적인 감을 잡는 

정도로만 보시기 바랍니다.




// 전역변수
HGLRC hRC1, hRC2;
CRITICAL_SECTION cs;

// 바인드 텍스쳐 함수를 멀티쓰레드용으로 새로 만듬
void BindTexture(unsigned int iTex)
{
EnterCriticalSection(&cs);
glBIndTexture(GL_TEXTURE_2D, iTex);
LeaveCriticalSection(&cs);
}



// 텍스쳐 갱신 쓰레드
wglMakeCurrent(hdc, hRC2); // 텍스쳐 갱신 쓰레드는 RC2 를 사용

if( WeNeedTexture ) {
unsigned int iTex;
glGenTextures(1, &iTex);
BindTexture(iTex);
//그림을 로딩해서 텍스쳐로 만든다
BindTexture(0);
// 후처리: 텍스쳐를 별도의 전역변수, 리스트, 테이블 등등에 저장하여 렌더링에 쓸 수 있도록 한다
}

// 메인 쓰레드
// 초기화
hRC1 = wglCreateContext(hdc);
hRC2 = wglCreateContext(hdc);
wglShareLists(hRC1, hRC2); // RC 들이 리스트 공간을 공유하게 한다
wglMakeCurrent(hdc, hRC1); // 렌더링 쓰레드는 RC1 을 사용

CreateThread(...); // 텍스쳐 갱신 쓰레드를 생성한다.

// 렌더링 루프
...
if( WeNeedRender ) {
unsigned int iTex = <전역변수 리스트 테이블 등등에서 해당 텍스쳐 번호를 가져온다>;
BindTexture(iTex);
// 폴리곤 렌더링
BindTexture(0); // 다그리고 나면 unbind 를 해준다
}
...

상위

비회원

 

전체글 글쓴이: 비회원 » 2006-10-20 19:03

저도 OpenGL로 공부를 시작해서 그런지 이 쓰레드에 관심이 가는데........

다이렉트X는 렌더링하면서 , 택스쳐를 업로딩하는게 가능한건가요?

상위

362496전체글: 91가입일: 2001-11-09 09:00

 

전체글 글쓴이: 362496 » 2006-10-21 09:56

D3DCREATE_MULTITHREADED 옵션을 주면 별도의 쓰레드에서 텍스쳐를 로딩하는 것이 가능합니다.

물론 현재 반쯤 로딩 중인 텍스쳐를 렌더링에 사용할 수는 없습니다.

상위

Locked 

 

 

 

=======================

=======================

=======================

 

 

 

 

출처: http://cluster1.cafe.daum.net/_c21_/bbs_search_read?grpid=10vj5&fldid=GXX7&datanum=115&contentval=&docid=10vj5%7CGXX7%7C115%7C20060305121559&q=OpenGL 

 

 

Visual C++에서 OpenGL 사용하기 

■ 개 요 

window NT계열에서는 OpenGL은 윈도우 운영시스템의 한 부분이 되었습니다. 현재 window95,98에서 OpenGL을 사용하기 위해서는 따로, 이에 해당되는 라이브러리를 설치해야 합니다. 


■ 시 작 

우선 알아야할점은, OpenGL을 윈도우에 세팅하는 최소한의 지식입니다. GDI가 이미지를 다루기 위해서 Device Context(DC)가 필요하듯이 OpenGL은 Rendering Context(RC)가 필요합니다. 하지만, GDI는 각각의 명령들이 DC를 통하는것과는 다르게 OpenGL는 그 개념이 다릅니다. 하나의 스레드 안에 RC가 세팅되면, 그 스레드안의 모든 OpenGL은 현재의 RC를 사용하게 됩니다. 만약 하나의 윈도우에 여러개의 RC가 존재할경우, 하나의 스레드에는 현재 적용되는 RC만이 적용되게 됩니다. 

다음은 RC를 사용하기위해서 세 개의 단계로 설명됩니다. 

1. 윈도우 픽셀형식을 설정함 

2. RC를 생성함 

3. RC current를 만듬 


Visual C++에서 프로젝트를 생성하는 단계는 다음과 같습니다. 

1. MFC AppWizard(exe)의 새로운 프로젝트 워크스페이스를 선택하고, 생성시킬 디렉토리와 프로젝트명을 지정합니다. 그리고, Create를 클릭합니다. 
2. 알맞은 Document형식을 선택합니다.(여기서는 Single Document Interface) 
3. Database support = None 
4. Compond Document Support = None 
5. Docking Toolbar = Off 

   Initial Status Bar = Off 
   Printing an Print Preview = Off 
   Context-Sensitive Help = Off 
   3D controls = On 

6. 나머지는 사용측면에 맞도록 설정하면됩니다. 




■ 준 비 

우선, OpenGL파일과 라이브러리를 프로젝트에 포함시키는 작업이 필요합니다. 
Project-Settings을 선택하고 Link에서 General 카테고리내에 Object/Library모듈 부분에 다음과 같은 내용을 포함합니다. 
opengl32.lib glu32.lib glaux.lib 
그리고, stdafx.h 파일을 선택한뒤 다음과 같은 부분을 추가합니다. 

#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows 
// headers 


#include // MFC core and standard components 
#include // MFC extensions 
#include 
#include 
#ifndef _AFX_NO_AFXCMN_SUPPORT 
#include // MFC support for Windows 95 Common Controls 
#endif // _AFX_NO_AFXCMN_SUPPORT 

OpenGL은 WS_CLIPCHILDREN과 WS_CLIPSIBLINGS 스타일이 필요로 하므로, OnPreCeate를 다음과 같이 수정합니다. 

BOOL CSampleView::PreCreateWindow(Createstruct& cs){ 

        cs.style |= (WS_CLIPCHILDREN | WS_CLIPSIBLINGS); 
        return CView::PreCreateWindow(cs); 



다음으로, RC를 생성하기위해 해야할 첫번째 세팅은 윈도우 픽셀형식을 설정하는것입니다. 픽셀형식은 메모리상에 윈도우가 표현하는 그래픽형태를 어떤식으로 묘사하는가에 대한 설정으로 볼수 있습니다. 파라메터는 색상, 버퍼방식, 그리고, 인터페이스를 포함하게 됩니다. 

우선 Project Workspace에서 View class(CSampleView)를 선택한뒤 마우스의 오른쪽 버튼을 눌러 “Add Function"을 선택하고, 아래와 같은 함수를 추가합니다. 

BOOL CSampleView::SetWindowPixelFormat(HDC hDC) 


        PIXELFORMATDESCRIPTOR pixelDesc; 
        pixelDesc.nSize = sizeof(PIXELFORMATDESCRIPTOR); 
        pixelDesc.nVersion = 1; 
        pixelDesc.dwFlags = PFD_DRAW_TO_WINDOW | 
        PFD_DRAW_TO_BITMAP | 
        PFD_SUPPORT_OPENGL | 
        PFD_SUPPORT_GDI | 
        PFD_STEREO_DONTCARE; 
        pixelDesc.iPixelType = PFD_TYPE_RGBA; 
        pixelDesc.cColorBits = 32; 
        pixelDesc.cRedBits = 8; 
        pixelDesc.cRedShift = 16; 
        pixelDesc.cGreenBits = 8; 
        pixelDesc.cGreenShift = 8; 
        pixelDesc.cBlueBits = 8; 
        pixelDesc.cBlueShift = 0; 
        pixelDesc.cAlphaBits = 0; 
        pixelDesc.cAlphaShift = 0; 
        pixelDesc.cAccumBits = 64; 
        pixelDesc.cAccumRedBits = 16; 
        pixelDesc.cAccumGreenBits = 16; 
        pixelDesc.cAccumBlueBits = 16; 
        pixelDesc.cAccumAlphaBits = 0; 
        pixelDesc.cDepthBits = 32; 
        pixelDesc.cStencilBits = 8; 
        pixelDesc.cAuxBuffers = 0; 
        pixelDesc.iLayerType = PFD_MAIN_PLANE; 
        pixelDesc.bReserved = 0; 
        pixelDesc.dwLayerMask = 0; 
        pixelDesc.dwVisibleMask = 0; 
        pixelDesc.dwDamageMask = 0; 
        m_GLPixelIndex = ChoosePixelFormat( hDC, &pixelDesc); 
        if (m_GLPixelIndex==0) // Let's choose a default index. 
        { 
                m_GLPixelIndex = 1; 
                if (DescribePixelFormat(hDC, m_GLPixelIndex, 
                        sizeof(PIXELFORMATDESCRIPTOR), &pixelDesc)==0){ 
                        return FALSE; 
                } 
        } 
       if (SetPixelFormat( hDC, m_GLPixelIndex, &pixelDesc)==FALSE) 
        { 
                return FALSE; 
        } 
        return TRUE; 


그런뒤 다시 Workspace의 view class에서 오른쪽 버튼을 누른뒤, "Add Variable"을 선택하여 다음과 같은 변수를 생성합니다. 

int m_GLPixelIndex; // protected 

마지막으로, WM_CREATE의 함수인 OnCreate()함수에 다음과 같은 코드를 삽입합니다. 

int CSampleView::OnCreate(LPCREATESTRUCT lpCreateStruct) 

        if (CView::OnCreate(lpCreateStruct) == -1) 
                return -1; 
        HWND hWnd = GetSafeHwnd(); 
        HDC hDC = ::GetDC(hWnd); 
        if (SetWindowPixelFormat(hDC)==FALSE) 
                return 0; 
        return 0; 


위와 같은 작업이 끝나면 OpenGL을 하는 최소한의 작업을 마친것입니다. 일단 한번 컴파일을 해봐서 syntex 에러를 잡고 다음으로 넘어가시기바랍니다. 

□ PIXELFORMATDESCRIPTOR 부분을 설명합니다. 이부분을 알경우는 넘어가셔도 됩니다. 
- dwFlags는 devices와 인터페이스를 pixel format부분에 조합에대해 설정하는부분입니다. 
PFD_DRAW_TO_WINDOW : 윈도우나 장치표면에 묘화가능 
PFD_DRAW_TO_BITMAP : 메모리상의 비트맵형식으로 묘화가능 
PFD_SUPPORT_GDI : GDI를 사용함(단, PFD_DOUBLEBUFFER를 사용시는 무시됨) 
PFD_SUPPORT_OPENGL : OPENGL 사용가능 
PFD_GENERIC_FORMAT : pixel format이 GDI라이브러리가 지원될경우 명시됨 
PFD_NEED_PALETTE 
PFD_NEED_SYSTEM_PALETTE 
PFD_DOUBLEBUFFER : 더블버퍼링의 사용여부를 결정(단 GDI와 공유못함) 
PFD_STEREO 
- iPixelType은 색상을 표현시 사용하는 방식을 설정합니다. 
PFD_TYPE_RGBA : 비트셋은 R,G,B값으로 사용함 
PFD_TYPE_COLORINDEX : 각 비트셋을 색상룩업테이블을 사용함 
- cColorBits은 색의 설정에 있어서 사용하는 비트수를 결정합니다. 
- cRedBits, cGreenBits, cBlueBits, cAlphaBits는 각 요소에 대해 비트수를 대표합니다. 
- cRedShift, cGreenShift, cBlueShift, cAlphaShift는 각 색상요소에대한 시작색값의 offset위치를 나타냅니다. 

우선 중요한것을 설명했습니다. 나머지 파라메터에 대해 궁금하시면, Visual C++의 Online메뉴얼을 참고하시기 바랍니다. 

m_hGLPixelIndex = ChoosePixelFormat(hDC, &pixelDesc); 

ChoosePixelForamt은 hDC와 PIXELFORMATDEXCRIPTOR를 수용하고, 이 형식을 참조할 인덱스값을 리턴합니다. (만약 실패할경우 0 값) 

형식이 설정되었으면,  RC를 생성하고, 설정해야합니다. 

BOOL CSampleView::CreateViewGLContext(HDC hDC) 

        m_hGLContext = wglCreateContext(hDC); 
        if (m_hGLContext == NULL) 
        { 
                return FALSE; 
        } 
        if (wglMakeCurrent(hDC, m_hGLContext)==FALSE) 
        { 
                return FALSE; 
        } 
        return TRUE; 


이 클래스에 아래 변수를 추가합니다. 
HGLRC m_hGLContext; // protected 

그리고, OnCreate에 위 함수를 호출하는 부분을 추가합니다. 
int CSampleView::OnCreate(LPCREATESTRUCT lpCreateStruct) 

        if (CView::OnCreate(lpCreateStruct) == -1) 
                return -1; 
        HWND hWnd = GetSafeHwnd(); 
        HDC hDC = ::GetDC(hWnd); 
        if (SetWindowPixelFormat(hDC)==FALSE) 
                return 0; 
        if (CreateViewGLContext(hDC)==FALSE) 
                return 0; 
        return 0; 


□ 파  괴 
void CSampleView::OnDestroy() 

        if(wglGetCurrentContext()!=NULL) 
        { 
                // make the rendering context not current 
                wglMakeCurrent(NULL, NULL) ; 
        } 
        if (m_hGLContext!=NULL) 
        { 
                wglDeleteContext(m_hGLContext); 
                m_hGLContext = NULL; 
        } 
        // Now the associated DC can be released. 
        CView::OnDestroy(); 



마지막으로 본 클래스의 생성자에 다음과 같이 추가합니다. 

CSampleView::CSampleView() 

        m_hGLContext = NULL; 
        m_GLPixelIndex = 0; 



여기까지, OpenGL의 환경설정부분이 끝났습니다. 이부분에서 역시 한번 컴파일해서, 오타가 났는지를 검사하고 넘어가시기바랍니다. 

위 프로그램을 실행시켜보면, 일반적인 MFC 플랫폼으로 보입니다. 하지만, 실질적으로는 OpenGL 묘화가 가능한 상태입니다. 이 코드들의 앞부분에서, RC를 생성을 했으며, 그리기를 할때마다, DC를 생성해야하는 GDI프로그램과는 다르게, 이후로는 자유롭게 OpenGL을 사용할수 있습니다. 

다음으로 각 메소드의 역할에 대해 알아보겠습니다. 

CreateViewGLContext는 현재의 RC를 생성합니다. wglCreateContext는 RC로 핸들을 넘겨줍니다. 그리고, DC와 관련된 픽셀형식은 CreateViewGLContext가 생성되기전에 선언되어야 합니다. wglMakeCurrent는 위에서 생성한 RC를 현재의 context로 지정합니다. 만일 다른 RC가 wglMakeCurrent에 의해 지정된경우에는 예전의 RC를 방출해 버리며(flush) 새로운것으로 교체시킵니다. 그리고, wglMakeCurrent(NULL, NULL)을 사용해서, RC를 만들지 않음을 지정할수도 있습니다. 

OnDestroy함수는 윈도우의 RC를 제거하기 때문에, 렌더링 콘텍스트를 이곳에서 삭제해야합니다. 하지만, RC를 제거하기 전에, 제거할 RC가 현재의 RC로 지정되지 않았는지를 확인해야 합니다. wglGetCurrentContext를 사용해서, 현재의 RC가 무엇인지를 알수 있습니다. 만일, 존재할경우에는 wglMakeCurrent(NULL, NULL)을 이용해서 그것을 제거해야합니다. 다음으로는 wglDeleteContext를 이용해서 RC를 제거합니다.(이 순서를 지키지않을경우, 따르는 피해는 엄마 엄마 할껍니다.) 

OpenGL에서의 팁들... 

1. viewport와 matrix mode는 WM_SIZE 메세지에 지정한다. 
2. 모든 묘화는 WM_PAINT메세지에서 행한다. 
3. RC를 생성하는것은 CPU를 많이 잡아먹으므로, 가급적 하나의 프로그램에 한번만 생성시키도록! 
4. document class에 묘화시키는 명령들을 캡슐화 시키도록 노력할것. 이것이 다른 뷰에서 같은 도큐먼트를 참조하는것이 가능하게 되니깐 말이얌.

 

 

 

 

=======================

=======================

=======================

 

 

 

 

출처: http://syzzanga.blog.me/50172227496

Render 및 UI그리는 작업은 Main Thread로 해야한다. 

1)  open gl을 사용하려는 thread는 각각 하나의 rendering context를 가져야하며 공유불가하다.

2)  Render관련 작업이 시간을 많이 잡아 먹는 작업이며, GPU와 CPU는 동시에 않기 때문에 하나의       어플리케이션 실행시 생성되는 Main Thread로 GPU가 병렬로 처리 하도록 해야 한다. 

3) 모든 UI Framework는 Thread safe하지 않다.

    때문에 안드로이드에서는 runUIThread는 UI작업을 mainthread에서 쓸수 있도록 큐에 쌓는 작업이  다.

 

출처 http://stackoverflow.com/questions/15178326/do-opengl-functions-cause-main-thread-to-freeze

No, they (mostly) do not. The majority of GL functions are buffered when used and actually executed later. This means that you cannot think of the CPU and the GPU as two processors working together at the same time. Usually, the CPU executes a bunch of GL functions that get buffered and, as soon as they are delivered to the GPU, this one executes them. This means that you cannot reliably control how much time it took for a specific GL function to execute by just comparing the time before and after it's execution.

If you want to do that, you need to first run a glFinish() so it will actually wait for all previously buffered GL calls to execute, and then you can start counting, execute the calls that you want to benchmark, call glFinish again to make sure these calls executed as well, and then finish the benchmark.

On the other hand, I said "mostly". This is because reading functions will actually NEED to synchronize with the GPU to show real results and so, in this case, they DO wait and freeze the main thread.

edit: I think the explanation itself answers the question you asked second, but just in case: the fact that all calls are buffered make it possible for a draw to complete first, and then change a setting afterwards for succesive calls

http://stackoverflow.com/questions/11097170/multithreaded-rendering-on-opengl

 

I have a multithreaded application, in which I'm trying to render with different threads.

DON'T!!!

You will gain nothing from trying to multithread your renderer. Basically you're running into one large race condition and the driver will just be busy synchronizing the threads to somehow make sense of it.

To gain best rendering performance keep all OpenGL operations to only one thread. All parallelization happens for free on the GPU.

 

 

출처: [http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=50&MAEULNo=20&no=320184&ref=320184]

 

 

 



꼭 갈켜 주세요 MFC에서 OpenGL 작업시에요...

전체글 글쓴이: 꼭이욤... » 2003-03-13 01:52
MFC방식으로 GL관련 프로그램을 짰습니다.
MAX5에서 작업한 인체 모델링 객체를 LOAD하였고,

한 축을 기준으로 카메라를 ZOOM IN ,OUT 하고 ROTATE 하는 간단한(아주 기본적인) , 그리고 TEXTURE

 를 입히는 작업을 했습니다.
RENDER SCENE는 OnDraw 함수에서 불러주고요, OnSize에서 clipping 영역 잡고 viewport 설정하고 -> 

일반적인 절차를 다 거쳤습니다.

물론 BackGround를 다시 그리지 않는다는 설정도 했구요 OnEraseBkgnd() 핸들러에서요...

그런데 내부적으로 Thread가 3개가 돌고 있어요(Primary Thread 포함)


사설이 길었군요... 자세히 상황 설명을 하다 보니 ^^& 이해해 주세요...

그런데 문제는..... Cam을 회전하거나, Zoom In ,Out 시에 너무 느리게 움직입니다.
참. 카메라 회전과 Zoom In, Out은 따로 Thread를 만들지 않고, 그냥 OnTimer에서 20/1000 로 호출됩니다.

왜 이런 현상이 발생하는 가요? 그리고 해결 할 수 있는 방법을 찾아주세요.
OnDraw도 시도 때도 없이 발생을 하더군요...
원래 OpenGL Library를 사용하면 그런가요?

잘 부탁 들립니다.



메시지가 난무하는 구조를 개선할 필요가...

전체글 글쓴이: 362496 » 2003-03-13 09:22
OnTimer->OnDraw 로 이어지는 메시지 기반의 프로그램 진행 방식은 애니메이션에는 맞지 않습니다. 메시지가 너무 많이
 날아다니거든요.

먼저... OnTimer 함수 속에서 Invalidate 를 하고, OnDraw가 호출되면 그려주는 방법을 사용하시는 것 같군요. 그러면 WM_PAINT 가

 시도때도없이 발생하고 그때마다 OnDraw가 호출됩니다. 메시지 발생을 조금이라도 줄이려면 OnTimer 함수 속에서 Invalidate를
 하지 말고 RenderScene을 직접 해주는 방법도 있습니다. 저는 아예 Invalidate 멤버를 재정의해서 그속에 RenderScene만 달랑 넣어서
 씁니다. 그러면 Invalidate를 해도 OnDraw가 호출되지 않으면서 RenderScene만 되겠지요...

그리고... 만들어진 모델이 평소에도 계속 움직이고 있는지요...? 그게 아니라면 타이머를 쓰지 말고, 카메라 위치가 바뀌었을 때에만 

화면을 다시 그리도록 하면 좀더 나을 텐데요... CPU 점유율도 많이 내려갈거고...

자세한 내용은 소스를 봐야 알겠지만, 대충 짐작되는 문제들은 이정도인것처럼 보입니다...

그럼 행운을...

상위
답변 감사합니다.

그런데....

전체글 글쓴이: 답변 감사합니다. » 2003-03-13 13:56
말씀하신데로, Invalidate를 재정의 해서 같은 방법을 사용하여 개선을 해 봤는데요, 똑같네요 ^^&
아무래도, 다른 2가지의 Thread에서 발생하는 문제 같네요...
Invalidate(FALSE)의 경우 BackGraound 를 그리지 않는다고 알고 있구요...
Time 함수를 걸어 주지 않는다면, 실시간적으로 Camera를 회전 시킬 수 있을지.. 저의 생각이 짧아서 ^^& 생각이

 잘 않나네요...^^

Dlg를 띄워서 OwnerDraw를 사용하여 만든 button을 이용해서 만든 카메라 회전 이거던요.. 회전 버튼을 누르면, SetTimer()가

 호출 되고요...

다른 문제가 아니라 BackGround에서 돌아가는 Thread문제 인거 같네요...
그 Thread를 최우선 순의 Thread로 줘서 그런 것 같은 생각이 드네요...

답변 감사 드립니다 그리구요...^^ 생각 나시는 해결 방법 있음 꼭 답변 주세요. ^^&

상위


//--------------------------------------------------------------

기타링크:

http://heilow.egloos.com/83533


http://higherorderfun.com/blog/2011/05/26/multi-thread-opengl-texture-loading/

 

 

 

=======================

=======================

=======================

 

 

 

 

반응형


관련글 더보기

댓글 영역