전산쟁이의 카피질

뒤로 검색

윈도우에서 쓰레드 사용 방법->절친 완전 설명 ( MFC thread 강좌 )

2009/10/16 10:05

윈도우에서 쓰레드 사용 방법->절친 완전 설명 ( MFC thread 강좌 )

오랫만에 정말 친절한 설명을 찾았다..
그동안 개념이 어리버리했는데 확실히 개념을 잡았다.

^^

출처 :

Threads and Thread synchronization
Threads
MFC는 2종류의 쓰레드로 구분할 수 있다.


1. user interface threads
메시지 루프가 존재한다. 윈도우를 만들고 이들 윈도우로 보내진 메시지들을 처리한다. 어플리케이션안에 또하나의 어플리케이션(ui-threads)을 만드는것과 비슷하다.일반적으로 별개로 움직이는 다중 윈도우를 만들때 많이 사용되어 진다.


2. worker threads
직접적으로 메시지를 받지 않고 백그라운드에서 동작되기 때문에 윈도우나 메시지루프들이 필요가 없다.

%이 둘간의실질적인 차이는 아직 잘모르겠다. 좀 더 학습하도록


-Creating a Worker Thread
AfxBeginThread함수는 ui-thread,worker thread 둘다 쓰인다. MFC프로그램에서 Win32::CreateThread함수를 사용하지 말아라.

ex)
CWinThread* pThread = AfxBeginThread (ThreadFunc, &threadInfo);

UINT ThreadFunc (LPVOID pParam)
{
    UINT nIterations = (UINT) pParam;
    for (UINT i=0; i<nIterations; i++);
    return 0;
}


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

nPriority : 쓰레드 우선순위. 쓰레드들 중에서만 상대적인 우선순위를 정할때 사용한다.
nStackSize : 쓰레드의 스택사이즈 라는데 정확히 뭔지..몰라.
dwCreateFlags : 0 일 경우, 이 함수 호출후 바로 쓰레드가 시작되는 것이고,
  CREATE_SUSPENDED일 경우, ResumeThread()가 호출되어야지만 시작되는것이다.
lpSecurityAttrs : 몰라도 되.


--Thread Function prototype
콜백함수이기에 static class 멤버 함수이거나 클래스 밖에 선언된 전역함수이어야 한다.
UINT ThreadFunc( LPVOIDpParam )
pParam : 32비트값. AfxBeginThread 함수의 파라미터. 이것은 scalar,handle,객체포인터로도 사용되어 질수 있다.


-Creating a UI Thread
CWinThread를 상속받은 클래스를 사용한다.

ex)
class CMyThread : public CWinThread

CMyThread * pThread = AfxBeginThread(RUNTIME_CLASS(CMyThread),
                                    THREAD_PRIORITY_NORMAL,
                                    0, // stack size
                                    CREATE_SUSPENDED);
pThread->초기값 설정.
pThread->ResumeThread();


또는

CMyThread * pThread = new CMyThread();
pThread->초기값 설정.
pThread->CreateThread();


-Suspending and Resuming Threads
SuspendThread를 호출하면 쓰레드는 멈추고, 내부적으로 Suspend count 가 1 증가 한다. 그리고 ResumeThread를 호출하면 Suspend count는 1 줄고 쓰레드는 다시 움직인다.
ResumeThread는 자신이 호출 할 수 없다. 그리고 이들 함수의 리턴값은 쓰레드의 이전 Suspend Count이다.


-Putting Threads to sleep
::Sleep(0)  (스레드 양보)
현재쓰레드를 중지하고 스케쥴러가 동등한 혹은 높은 우선순위를 갖는 다른 쓰레드를 움직이도록 허락해준다. 만약, 다른 동등한 혹은 높은 우선순위의 쓰레드가 wait 상태가 아니라면, 이 함수는 바로 리턴해서 현재쓰레드를 재시작 한다. (::SwitchToThread.(in NT 4.0 ), ::Sleep(0) (in win32 ))
Sleep함수의 시간은 정확하지 않을 수 있다. 이는 여러환경에 지배받기때문이다. 윈도우즈에서는 쓰레드의 suspend 시간을 보장하는 함수는 존재하지 않는다.



-Terminating a Thread

--Worker Thread
call AfxEndThread.
쓰레드 함수내부의 리턴으로 종료.


 

--UI Thread
call AfxEndThread.
post WM_QUIT.( ::PostQuitMessage )

(쓰레드 종료함수 사용시 주의할것은 쓰레드 내부에 메모리를 동적할당(new,malloc)해놓고 delete를 안해서 메모리 릭이 날 염려가 있다. 그래서 가급적이면 쓰레드 함수 리턴으로 종료하는 것이 낫다.)

위의 함수들을 호출한 후 한번 제대로 종료됬는지 확인해보라
DWORD dwExitCode;
::GetExitCodeThread (pThread->m_hThread, &dwExitCode);

만약 여전히 살아 있다면 dwExitCode = STILL_ACTIVE(0x103) 으로 되어 있을테다.



 

- CWinThread 개체 자동 삭제하기

AfxBeginThread 로 스레드 생성할 경우,


 

CWinThread *pThread = AfxBeginThread( , ,, );


 

위와 같이 CWinThread 개체 포인터를 반환값으로 받는다. 스레드 종료시, 이 개체는 어떻게 되는가?

MFC는 사용자가 따로 delete pThread; 할 필요없게 자동으로 삭제해 준다.

그런데, 여기서 문제가 있다.

스레드가 종료되지 않았다면, 아래구문은 잘못된 곳이 없다.


 

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

( 스레드의 현재 상태값을 반환하는 함수 )


 

하지만, 종료되어 pThread 개체 포인터 역시 자동으로 삭제되었다면, 위의 구문은 에러를 발생할 것인다.

pThread 는 아무것도 가리 키지 않기 때문에..


 

해결책)

1. m_bAutoDelete = FALSE; 설정.

-- 이는 곧 사용자가 CWinThread 개체를 delete 해주어야 함을 의미한다.

-- 스레드가 중지가 된 상태에서 m_bAutoDelete = FALSE; 를 설정해주어야 한다.


 

2. Win32::DuplicateHandle 함수를 호출하여 스레드 핸들의 사본을 생성하여 사용.

--해당핸들의 참조카운트가 1 -> 2로 증가되어 CloseHandle() 호출시에 다시 2 -> 1 로 감소될뿐 개체는 죽지 않고 남아있다. 물론, 사용자가 명시적으로 CloseHandle() 을 호출해야 한다.


 

- 다른 스레드 종료하기

1.

//Thread A

nContinue = 1;

CWinThread *pThread = AfxBeginThread( ThreadFunc, &nContinue );

...

...

nContinue = 0 ; // 스레드 B 를 종료해라.



 

//Thread B

UINT ThreadFunc( LPVOID pParam )

{

  int* pContinue = (int*) pParam;

  while( *pContinue )

  {

    ....

  }

  return 0;

}



 

2. 스레드 A는 스레드 B가 죽을때 까지 무한 기다리도록 하고 싶을 경우,


 

//Thread A

nContinue = 1;

CWinThread *pThread = AfxBeginThread( ThreadFunc, &nContinue );

HANDLE hThread = pThread->m_hThread; //사본 생성. 스레드B종료시 pThread 없을 경우 대비.

...

...

nContinue = 0 ; // 스레드 B 를 종료해라.

::WaitForSingleObject( hThread, INFINITE );



 

//Thread B

//1.의 에제와 같음



 

::WaitForSingleObject 은 hThread 스레드가 종료될때 까지 무한정(INFINITE) 기다리는 함수이다.  반환 값은 아래와 같다.

-- WAIT_OBJECT_0 : 그 개체가 신호를 받았다. 즉, 스레드가 종료되었다.

-- WAIT_TIMEOUT : 스레드는 살아있지만, 시간 만료로 기다리지 않고 반환되었다.


 

두번째 매개 변수를 0 으로 설정하고 아래와 같이 사용하는 것이 좋다.


 

if( ::WaitForSingleObject( hThread, 0 ) == WAIT_OBJECT_0 )

//스레드가 더이상 존재치 않는다.

else

//스레드가 여전히 실행중이다.



 

- ::TerminateThread( hThread, 0 );

다른 스레드를 직접삭제하는 방법은 위의 함수 딱 한가지 존재한다. 어쩔 수 없는 경우에만 사용하도록 .



MFC에서 스레드를 사용하기 전에 알아두어야 할 함수는 AfxBeginThread, AfxEndThread 이렇게 두 함수가 있다. 이 함수에 대한 인자값은 MSDN을 참고하기 바라며, 간단한 사용법을 알아보자.

AfxBeginThread(CalcIt, (LPVOID)val);

첫 번째 인자는 전역함수인데, 클래스에 포함시킬 경우 정적함수로 선언해야되고, 아니면 전역함수로 선언해서 써야한다. CalcIt 함수의 리턴값과 파라미터는 아래와 같다.

UINT CalcIt(LPVOID arg);

꼭 위의 규칙을 따라줘야 한다.

일반적으로는 위와 같이 스레드를 시작하면 된다. 그러면 스레드가 시작되고, CalcIt함수(코어 함수??)의 역할이 끝나면 자동으로 스레드를 종료한다.

하지만 스레드는 위와 같이 쓸 경우 바로 리턴해버린다. 리턴값이 0이면 정상적인 종료를 뜻하게 되는데, 사용자에게 의미있는 리턴값을 받기 위해서, 이렇게 하면 안될것이다.

그래서 사용자가 원하는 값을 리턴 받으려면 아래와 같은 방법을 쓴다.

CWinThread* pThread = AfxBeginThread(CalcIt, (LPVOID)val, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
우선 스레드를 시작하지만 정지상태로 놓는다는 뜻이다.(CREATE_SUSPENDED)

그 다음
pThread->m_bAutoDelete = FALSE;
위에서 말했듯이, 스레드가 시작되고 자기 할일이 끝나면 바로 스레드는 종료된다. 하지만 이 값을  FALSE로 해주면 종료되지 않는다. 대신 사용자가 직접 원하는 시점에 종료를 해줘야 한다.(AfxEndThread)

pThread->ResumeThread();
이제 스레드를 시작한다.

그럼 리턴값을 받아야한다.

::GetExitCodeThread(pThread->m_hThread, &returnvalue); //Get ExitCode from thread.

이 함수는 스레드로 부터 리턴값을 읽어온다. 여기에 포함되는 리턴값을 받기 위해선 위의 CalcIt 함수에 AfxEndThread를 써줘야 한다. AfxEndThread함수의 첫 번째 인자는 위 함수의 returnvalue에 들어갈 리턴값이다. 두번째 인자는 스레듣 객체를 메모리에서 제거할 것인지를 나타낸다. 기본값은 TRUE이고 메모리에서 삭제된다. FALSE이면 메모리에 남아있게 되어 스레드 객체를 재사용 할 수 있다.

그리고 마지막으로 알아둬야 할 것이, WaitForSingleObject 함수이다.

역할은 Sleep과 동일하지만, 이 함수의 첫 번째 인자에 있는 스레드가 사용할 수 있게 될 때까지 기다리는 것이다.

Sleep은 스레드를 멈추는 역할밖에 못하지만, WaitForSingleObject는 특정 시간동안 이벤트를 감지할 수 있습니다.

WaitForSingleObject 의 두 번째 인자에는 밀리세컨드 단위의 시간이 들어가는데, INFINITE가 들어가면 스레드가 끝날때까지 기다리게 됩니다.

만약 구현이 된다면 아래와 같은 구조가 될 것이다.

  pThread = AfxBeginThread(ExportVVF, &arg1, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); //Make Thread with suspension
pThread->m_bAutoDelete = FALSE; //if this thread will be end, object will not destory this thread.
  pThread->ResumeThread(); //Begin Thread
WaitForSingleObject(pThread->m_hThread, INFINITE);
::GetExitCodeThread(pThread->m_hThread, &returnvalue); //Get ExitCode from thread.


써놓고 보니 정신없네;;;

[출처] AfxBeginThread 사용법|작성자 큐티뽀




요기서 부터는 내가 첨부

Sleep( 0 ) 는 스레드 스위칭이 확실히 100% 되지 않는다.(컨텍스트 전환, 콘텍스트전환, context switching ; 검색되게 하기위해 넣음)
왜? 운영체계한테 제어권을 넘기는 거라고 볼 수 있기 때문이다. 운영체계가 알아서 넘기기 때문에 해당 스레드가 우선순위가 높으면 다시 해당 스레드를 실행 시키게 되는 경우가 생긴다.
SwitchToThread()를 쓰면 해결~

Tags

AfxBeginThread, context, context switch, context switching, CWinThread, ResumThread, Sleep(0), SuspendThread, Thread, WaitForSingleObject, WAIT_OBJECT_0, 스레드, 스레드 종료, 쓰레드, 양보, 컨텍스트, 컨텍스트 전환, 콘텍스트, 콘텍스트 전환
이 페이지는 Textcube 1.10.0 : beta 1 로 구동됩니다 데스크탑 화면