본문 바로가기

대학원 수업관련

Thread 사용법, CreateThread() / _BeginThreadex()

1. Thread란?

스레드는 프로세스 내의 수행 흐름을 의미한다. 프로그램이 초기화가 진행되는 동안에 시스템은 주 스레드를 생성하므로 하나의 프로세스에는 둘 이상의 스레드가 수행될 수 있다. 이러한 스레드들은 단일 주소 공간을 공유하게 된다. 스레드들은 동일한 코드를 수행할 수도 있고, 동일한 데이터를 조작할 수도 있다. 커널 오브젝트 핸들 테이블은 스레드별로 존재하는 것이 아니고 프로세스별로 존재하기 때문에, 스레드들은 커널 오브젝트 핸들도 역시 공유한다.

⑴ 스레드를 사용하는 이유
 과연 스레드는 왜 사용하는 것일까? 이유는 간단하다. 한번에 여러가지 작업을 하기 위해서이다. 만약 당신이 간단하게 코딩을 해보았다면, CPU는 바보 그자체라서 한번에 한가지 일 밖에 못한다는 것을 알것이다. 무한 루프로 계속 어떤 작업을 감시해야 한다면 사용자는 그 작업이 실행되는 동안 더 이상 제어를 하지 못하는 것을 많이 보았을 것이다. 그렇다면 무한 루프로 계속 감시를 하면서 사용자가 제어를 하기 위해서는 어떻게 해야 할까? 바로 멀티스레딩을 하는 것이다. 프로세스에서 스레드들에게 수행 흐름(시간)을 나누워서 번갈아가며 수행하는 것이다. 사실 CPU는 한번에 하나의 일을 하지만 여러 작업을 번갈아 가면서 수행하므로 전체를 보았을 때에는 동시에 처리하는 것처럼 보이는 것이다.

 실제로 우리가 문서 작업을 하다보면 철자와 문법 검사라던가, 페이지 번호등 우리가 키보드를 타이핑하는 동안 자동으로 해당 어플리케이션이 백그라운드에서 수행을 하면서 동시에 작업을 할 수 있는 것이다. 또한 별도로 사용자가 검사를 할 필요가 없으므로, 메뉴에 따로 필요도 하지 않게 되어 사용자 인터페이스도 더 단순화해진다.
 
 이처럼 사용자 인터페이스와 별도로 백그라운드에서 작업을 수행하여 사용자에게 더 즉각적인 응답을 보이도록 작성 할 수 있는 것이다.

 물론 멀티스레딩 외에도 멀티프로세스도 가능하지만 스레드에 비해 프로세스는 더욱 더 많은 시스템 리소스를 사용한다. 프로세스별로 가상 주소 공간을 생성하는 것은 매우 많은 시스템 리소스를 필요로 한다. 특히 개별 프로세스는 상당량의 정보를 시스템 내부에 저장해 두어야 하기 때문에 메모리를 많이 필요로 한다. 또한 .exe와 .dll 파일이 주소 공간으로 로드되어야 하므로 파일 리소스 또한 필요하다. 반면 스레드는 프로세스에 비해 상당히 적은 시스템 리소스를 필요로 한다. 사시르 스레드는 단지 하나의 커널 오브젝트와 스레드 스택 정도만을 필요로 할 뿐이다. 시스템 내부에 저장해 두어야 하는 내용도 비교적 적고, 메모리도 덜 차지한다.

2. CreateThread 함수
    

⑴ 스레드를 수행하는 함수
  새로 생성되는 스레드는 아래와 같은 형태의 진입점 함수를 반드시 가져야 한다.

DWORD WINAPI ThreadFunc (PVOID pvParam)
{
DWORD dwResult = 0;
......
return (dwResult);
}

.
.

⑵ 스레드 생성 및 닫기

DWORD dwThreadID;
HANDLE hThread = CreateThread( NULL, 0, ThreadFunc, this, &dwThreadID);
GetExitCodeThread(hThread, &dwThreadID);
CloseHandle(hThread);


⑶ CreateThread 함수 자세히 알아보기
HANDLE CreateThread(
PSECURITY_ATTRIBUTES psa,  //스레드 커널 오브젝트 보안 특성 기본 'NULL'
DWORD cbStackSize,               //스레드 스택 크기 디폴트 '0'
PTHREAD_START_ROUTINE pfnStartAddr,  //실행되는 함수
PVOID pvParam,                     //실행되는 함수에 넘겨지는 매개변수
DWORD dwCreateFlags,            //스레드 제어하는 플래그
PDWORD pdwThreadID);            //스레드의 ID값


3. Thread 종료 방법
 

스레드는 4가지 방법으로 종료될 수 있다.
  • 스레드 함수가 반환된다. (추천)

  • 스레드 함수 내에서 ExitThread 함수를 호출한다.

  • 동일한 프로세스나 다른 프로세스에서 TerminateThread 함수를 호출한다.

  • 스레드가 포함된 프로세스가 종료된다.


4. _beginthreadex를 사용하자
 

새로운 스레드를 생성할 때는 운영체제가 제공하는 CreateThread 함수를 호출하기 보다는 C/C++ 런타임 라이브러리가 제공하고 있는 _beginthreadex함수를 호출하는 것이 좋다. CreateThread 함수의 경우 새로운 스레드가 생성되었을 때 어떻게 새로운 데이터 블록을 할당해야 할지에 대해 모른다. 시스템은 어플리케이션이 C/C++로 개발되었는지, 멀티스레드환경으로 안전한 함수가 호출되었는지 전혀 알지 못한다. 따라서 개발자는 이 모든 것이 정상적으로 수행될 수 있도록 해주어야 하는 막중한 책임이 있다. _beginthreadex함수는 이것을 지원하기 때문이다.

_beginthreadex 함수는 CreateThread함수와 동일한 매개변수를 가지고 있지만 매개변수의 이름이나 형태가 일치하지 않는다. 하지만 반환값은 새로 생성되는 스레드의 핸들 값을 반환한다. 따라서 다음과 같이 매크로로 작성을 하면  CreateThread함수를 호출하는 코드를 쉽게 _beginthreadex로 대체할 수 있다. 

#include <process.h>

typedef unsigned
  (__stdcall *PTHREAD_START)  (void *);

#define  BEGINTHREADEX ( psa, cbStack, pfnStartAddr, pvParam, fdwCreateFlags, pdwThreadID )  \
((HANDLE) _beginthreadex(                                      \
(void *)  (psa),                                            \
(unsigned) (cbStack),                                  \
(PTHREAD_START) (pfnStartAddr),                \
(void *) (pvParam),                                      \
(unsigned) (fdwCreateFlags),                        \
(unsigned*) (pdwThreadID)))

5._beginthreadex() 사용시 에러 해결

* _beginthreadex()

 ; Win32용 API로 원래 CreateThread()가 있지만 이 함수는 완전히 Win32용 API와의 호환성을 지원하는 목적으로 만들어졌기 때문에 윈도우의 서브 시스템의 하나인 POSIX와 호환성을 보장하지 못한다. 그래서 윈도우에서는 POSIX C/C++ 라이브러리와 호환성을 보장하기 위해 '_beginThreadex()'함수를 지원한다.

- '_beginthreadex' : undeclared identifier

- 'unresolved external symbol'

 => 비주얼 스튜디오 6.0에서는 단일 스레드 라이브러리 상태가 기본적으로 설정되어있기 때문에 위와 같은 에러 발생시 다음과 같이 설정해본다.

 > Project - Settings


 - Category => Code Generation

- USE run-time library -> Multithreaded DLL

 
6. CreateThread()와 _beginThreadex()의 형식 비교

 

#include <windows.h>

HANDLE CreateThread(                                                              //리턴 타입 HANDLE (void*)

             LPSECURITY_ATTRIBUTES lpThreadAttributes,        //보안 디스크립터(구조체)

             SIZE_T dwStackSize,                                                        //초기 스택 사이즈 (unsigned long)

             LPTHREAD_START_ROUTINE lpStartAddress,           //스레드 함수 모듈 (반환형 DWORD)

             LPVOID lpParameter,                                                      //스레드 인자 (void*)

             DWORD dwCreationFlags,                                              //생성 옵션 (unsigned long)

             LPDWORD lpThreadID                                                       //스레드 ID (unsigned long*)

);

 => 주로 인자의 자료형이 DWORD(unsigned long)이다.

  

#include <process.h>

unsigned long _beginthreadex(                                        //리턴 타입 unsigned long

             void* security,                                                          //보안 변수 (void*)

              unsigned stack_size,                                               //초기 스택 사이즈 (unsigned int)

              unsigned (*start_address)(void*),                     //스레드 함수 모듈 (반환형 UINT)

              void* arglist,                                                             //스레드 인자 (void*)

              unsigned initflag,                                                     //생성 옵션 (unsigned int)

              unsigned* thrdaddr                                                 //스레드 ID (unsigned int*)

);

 => 주로 인자의 자료형이 unsigned int 타입이다. (int는 생략 가능하므로...) 윈도우에서는unsigned int를 UINT로 매크로 정의하고 있다. 두 함수의 인자를 비교해보면 아무 형식이나 입력 가능해야 하는 스레드 인자를 제외하고는 모두 데이터 타입이 다르다. 그리고 애시당초 헤더 파일도 다르다...

 

 그럼 간단한 스레드 생성의 예를 보자.

- CreateThread()...의 경우

#include <windows.h>

                    ...

DWORD WINAPI ThreadFunc(void *arg)

{

     //스레드 구현

}

                   ...

HANDLE hThread;
DWORD dwThreadID;

                   ...
{

     hThread = CreateThread(NULL, 0, ThreadFunc, NULL, 0, &dwThreadID);             //스레드 생성

}

                   ...

=> 스레드 함수의 리턴 타입(DWORD), 스레드ID(DWORD)의 데이터 타입을 유의하자.

  

- _beginthreadex()...의 경우

#include <process.h>

                ...

UINT WINAPI ThreadFunc(void *arg)
{

      //스레드 구현

}

                ...

HANDLE hThread;
UINT dwThreadID;

                ...

{

     hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, &dwThreadID);

}                ...

=> CreateThread()와 데이터 타입이 다르므로 주의한다스레드의 리턴 타입(UINT), _beginthreadex()의 리턴 타입은 HANDLE 타입이 아니므로 타입 캐스팅을 해야한다. 그리고 스레드ID의 타입(UINT)을 유의해야한다.


 - Reference

   http://no1rogue.blog.me/30096664831 

'대학원 수업관련' 카테고리의 다른 글

Thread를 이용한 Matrix Multiply  (0) 2014.04.03
Thread 함수 참고  (0) 2014.03.31
CreateThread, _beginthread, _beginthreadex의 차이  (0) 2014.03.29
Thread 발표 참고 링크  (0) 2014.03.28
Thread 관련 링크  (0) 2014.03.26