이현창 (musclyhc@hotmail.com)
쓰레드 핸들의 특징
쓰레드를 생성하자마자 CloseHandle()을 호출하는 것이 쓰레드를 종료하게 만들지는 못한다. 단순히 클라이언트에 주어진 레퍼런스만 하나 감소하고 쓰레드는 여전히 동작하게 된다. 나중에라도 이 클라이언트가 쓰레드의 상태를 검사할 일이 없다면, 굳이 HANDLE을 보관할 필요가 없기 때문에 오히려 쓰레드를 생성하자마자 CloseHandle()을 호출하는 것이 바람직할 수도 있다. 마찬가지로 쓰레드가 종료된 이후에도 클라이언트가 아직 쓰레드 핸들을 닫지 않았다면 핸들의 레퍼런스 카운트는 1이 되어 아직 살아있게 된다. 이 경우에 쓰레드는 종료되었지만 쓰레드 핸들을 가지고 작업을 하는 것은 가능하다. 예를 들어 GetExitCodeThread()를 사용해 쓰레드의 반환 값을 확인할 수 있다. 물론 쓰레드 핸들을 닫은 후에는 이러한 작업이 불가능하다.
HANDLE로 다뤄지는 커널 객체들의 또 한 가지 공통점은 신호를 받은 상태 혹은 신호를 받지 않은 상태를 가지고 있다는 점이다. 예를 들어 이벤트의 경우에는 SetEvent()를 했을 때 신호를 받은 상태가 되고 ResetEvent()를 했을 때 신호를 받지 않은 상태가 된다. 프로세스나 쓰레드 핸들의 경우에는 처음 생성시에는 신호를 받지 않은 상태이고 종료된 경우에 신호를 받은 상태가 된다. 이러한 상태를 활용하는 대표적인 API가 바로 WaitForSingleObject(), WaitForMultipleObjects(),
MsgWaitForMultipleObjects()이다. 이러한 API는 HANDLE을 입력 인자로 가지고 있는데, 입력된 핸들이 신호를 받을 때까지 기다리는 기능을 수행한다. 예를 들어 이벤트 핸들의 경우에는 이벤트가 셋(SET)될 때까지 기다렸다가 번환하며, 프로세스나 쓰레드의 경우에는 종료할 때까지 기다렸다가 반환한다. <리스트 1>처럼 main() 함수의 끝부분에 WorkerThread가 종료할 때까지 기다리는 코드를 볼 수 있을 것이다.
CRT를 올바르게 사용하자
CRT(C Run-Time Library)는 우리가 처음 C++ 언어를 배울 때부터 사용하던 많은 함수와 매크로가 포함되어 있다. 대표적인 printf() 함수부터 시작해서 메모리를 할당/해제 함수, 전역 변수의 생성자를 호출해 주는 기능까지 모두가 CRT의 기능이다. 이 CRT는 lib나 dll의 형태로 프로그램에 링크되는데, 성능상의 이유로 인해 다중 쓰레드 버전과 단일 쓰레드 버전으로 나눠져 있다. 다중 쓰레드 버전에서는 단일 쓰레드 버전과는 달리 동기화 코드가 추가되어 있는데, 프로그램이 단일 쓰레드만 가지고 있다면 굳이 이런 동기화 코드로 인한 부담을 껴안을 필요는 없다. 반면에 다중 쓰레드를 사용하는 경우에는 이런 동기화 코드가 반드시 필요하다. 즉, 자신의 쓰레드 사용 여부에 맞는 버전의 CRT를 링크할 필요가 있다는 것이다. <화면 1>과 <화면 2>는 비주얼 스튜디오 닷넷에서 CRT의 종류를 고를 수 있는 등록정보 창과 CRT의 종류를 보여주고 있다.

<화면 1> CRT의 종류를 보여주는 등록정보 창

<화면 2> CRT의 종류
<리스트 2>는 CRT를 올바르게 선택하는 것이 중요하다는 사실을 보여주기 위해 준비한 예제다. GetLastError() API와 비슷하게 CRT에도 현재 쓰레드의 최근 에러 값을 보관하고 있는 errno 변수가 존재한다. <리스트 2>를 단일 쓰레드 버전의 CRT와 링크한 경우에는 작업 쓰레드의 최근 에러 값이 엉뚱하게 나오는 것을 확인할 수 있다.
마이크로소프트에서 제공하는 CRT에는 _beginthread와 _beginthreadex 두 가지의 쓰레드 생성 함수가 있다. 이 함수들이 성공적으로 호출되면 정수 값을 반환하는데, 타입은 다르지만 쓰레드의 핸들이다. _beginthread를 사용한 경우에 생성된 쓰레드에서는 _endthread를 호출해 자기 자신을 종료시킬 수 있는데 이때는 자동으로 CloseHandle()을 호출해 쓰레드 핸들을 닫아준다. 반면에 _beginthreadex와 _endthreadex를 사용한 경우에는 직접 CloseHandle()을 호출해 줄 필요가 있다. 물론 MSDN에 잘 설명되어 있지만 모르고 지나치기 쉬운 부분이니 주의해서 사용하길 바란다.
쓰레드 핸들의 특징
쓰레드를 생성하자마자 CloseHandle()을 호출하는 것이 쓰레드를 종료하게 만들지는 못한다. 단순히 클라이언트에 주어진 레퍼런스만 하나 감소하고 쓰레드는 여전히 동작하게 된다. 나중에라도 이 클라이언트가 쓰레드의 상태를 검사할 일이 없다면, 굳이 HANDLE을 보관할 필요가 없기 때문에 오히려 쓰레드를 생성하자마자 CloseHandle()을 호출하는 것이 바람직할 수도 있다. 마찬가지로 쓰레드가 종료된 이후에도 클라이언트가 아직 쓰레드 핸들을 닫지 않았다면 핸들의 레퍼런스 카운트는 1이 되어 아직 살아있게 된다. 이 경우에 쓰레드는 종료되었지만 쓰레드 핸들을 가지고 작업을 하는 것은 가능하다. 예를 들어 GetExitCodeThread()를 사용해 쓰레드의 반환 값을 확인할 수 있다. 물론 쓰레드 핸들을 닫은 후에는 이러한 작업이 불가능하다.
HANDLE로 다뤄지는 커널 객체들의 또 한 가지 공통점은 신호를 받은 상태 혹은 신호를 받지 않은 상태를 가지고 있다는 점이다. 예를 들어 이벤트의 경우에는 SetEvent()를 했을 때 신호를 받은 상태가 되고 ResetEvent()를 했을 때 신호를 받지 않은 상태가 된다. 프로세스나 쓰레드 핸들의 경우에는 처음 생성시에는 신호를 받지 않은 상태이고 종료된 경우에 신호를 받은 상태가 된다. 이러한 상태를 활용하는 대표적인 API가 바로 WaitForSingleObject(), WaitForMultipleObjects(),
MsgWaitForMultipleObjects()이다. 이러한 API는 HANDLE을 입력 인자로 가지고 있는데, 입력된 핸들이 신호를 받을 때까지 기다리는 기능을 수행한다. 예를 들어 이벤트 핸들의 경우에는 이벤트가 셋(SET)될 때까지 기다렸다가 번환하며, 프로세스나 쓰레드의 경우에는 종료할 때까지 기다렸다가 반환한다. <리스트 1>처럼 main() 함수의 끝부분에 WorkerThread가 종료할 때까지 기다리는 코드를 볼 수 있을 것이다.
CRT를 올바르게 사용하자
CRT(C Run-Time Library)는 우리가 처음 C++ 언어를 배울 때부터 사용하던 많은 함수와 매크로가 포함되어 있다. 대표적인 printf() 함수부터 시작해서 메모리를 할당/해제 함수, 전역 변수의 생성자를 호출해 주는 기능까지 모두가 CRT의 기능이다. 이 CRT는 lib나 dll의 형태로 프로그램에 링크되는데, 성능상의 이유로 인해 다중 쓰레드 버전과 단일 쓰레드 버전으로 나눠져 있다. 다중 쓰레드 버전에서는 단일 쓰레드 버전과는 달리 동기화 코드가 추가되어 있는데, 프로그램이 단일 쓰레드만 가지고 있다면 굳이 이런 동기화 코드로 인한 부담을 껴안을 필요는 없다. 반면에 다중 쓰레드를 사용하는 경우에는 이런 동기화 코드가 반드시 필요하다. 즉, 자신의 쓰레드 사용 여부에 맞는 버전의 CRT를 링크할 필요가 있다는 것이다. <화면 1>과 <화면 2>는 비주얼 스튜디오 닷넷에서 CRT의 종류를 고를 수 있는 등록정보 창과 CRT의 종류를 보여주고 있다.


<리스트 2>는 CRT를 올바르게 선택하는 것이 중요하다는 사실을 보여주기 위해 준비한 예제다. GetLastError() API와 비슷하게 CRT에도 현재 쓰레드의 최근 에러 값을 보관하고 있는 errno 변수가 존재한다. <리스트 2>를 단일 쓰레드 버전의 CRT와 링크한 경우에는 작업 쓰레드의 최근 에러 값이 엉뚱하게 나오는 것을 확인할 수 있다.
<리스트 2> CRT의 사용 예
#include
#include
#include
using namespace std
DWORD WINAPI WorkThread(LPVOID);
void main()
{
// Result too large(34) 에러를 발생시킨다.
pow( INT_MAX, INT_MAX);
// 메인 쓰레드의 최근 에러 번호 출력
cout << "Main : " << errno << endl
// 작업 쓰레드 시작
DWORD dwID
HANDLE h = CreateThread(NULL, 0, WorkThread, NULL, 0, &dwID);
// 종료 처리
WaitForSingleObject(h, INFINITE);
CloseHandle(h);
}
DWORD WINAPI WorkThread(LPVOID)
{
// 작업 쓰레드의 최근 에러 번호 출력
cout << "WorkThread : " << errno << endl
return 0
}
// 결과 1 : 다중 쓰레드 버전의 CRT와 링크한 경우
Main : 34
WorkThread : 0
// 결과 2 : 단일 쓰레드 버전의 CRT와 링크한 경우
Main : 34
WorkThread : 34
#include
#include
#include
using namespace std
DWORD WINAPI WorkThread(LPVOID);
void main()
{
// Result too large(34) 에러를 발생시킨다.
pow( INT_MAX, INT_MAX);
// 메인 쓰레드의 최근 에러 번호 출력
cout << "Main : " << errno << endl
// 작업 쓰레드 시작
DWORD dwID
HANDLE h = CreateThread(NULL, 0, WorkThread, NULL, 0, &dwID);
// 종료 처리
WaitForSingleObject(h, INFINITE);
CloseHandle(h);
}
DWORD WINAPI WorkThread(LPVOID)
{
// 작업 쓰레드의 최근 에러 번호 출력
cout << "WorkThread : " << errno << endl
return 0
}
// 결과 1 : 다중 쓰레드 버전의 CRT와 링크한 경우
Main : 34
WorkThread : 0
// 결과 2 : 단일 쓰레드 버전의 CRT와 링크한 경우
Main : 34
WorkThread : 34
마이크로소프트에서 제공하는 CRT에는 _beginthread와 _beginthreadex 두 가지의 쓰레드 생성 함수가 있다. 이 함수들이 성공적으로 호출되면 정수 값을 반환하는데, 타입은 다르지만 쓰레드의 핸들이다. _beginthread를 사용한 경우에 생성된 쓰레드에서는 _endthread를 호출해 자기 자신을 종료시킬 수 있는데 이때는 자동으로 CloseHandle()을 호출해 쓰레드 핸들을 닫아준다. 반면에 _beginthreadex와 _endthreadex를 사용한 경우에는 직접 CloseHandle()을 호출해 줄 필요가 있다. 물론 MSDN에 잘 설명되어 있지만 모르고 지나치기 쉬운 부분이니 주의해서 사용하길 바란다.
"C++" 카테고리의 다른 글
- 다중 쓰레드와 C++ - 5 (0)2005/09/03
- 다중 쓰레드와 C++ - 4 (0)2005/09/03
- 다중 쓰레드와 C++ - 3 (0)2005/09/03
- 다중 쓰레드와 C++ - 2 (0)2005/09/03
- 다중 쓰레드와 C++ - 1 (0)2005/09/03

수안이의 컴퓨터 연구실



Leave your greetings.