[Clip]C++ 윈도우즈 기반의 쓰레드 생성


윈도우즈는 기본적인 프로그램의 실행 단위가 쓰레드이다. 예를 들어 우리가 모니터에다가

"Hello World!" 메시지를 출력하는 간단한 프로그램을 구현했다고 해 보자. 분명히 main 함수가

있을 것이다. 컴파일하고 나서 실행하게 되면 윈도우즈 OS는 일단 프로세스를 생성한다.

그리고 프로세스 내부에 메인 쓰레드란 것을 하나 생성한다. 결론적으로는 이 쓰레드가 main 함수를

실행하게 되는것이다.

즉 프로그램의 시작점인 main을 실행하는 것은 프로세스가 아니라. 프로세스 내부에 존재 하는

쓰레드이다. 따라서 프로세스는 프로그램을 실행시키는 일의 단위라고 하기 보다는 생성된 쓰레드를

담고 있는 저장소라고 할 수 있다. 이 부분이 유닉스 계열 운영체제와의 차이점 중 하나이다.


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

 

===== 쓰레드를 생성하자 =====

 

쓰레드도 커널에 의해서 생성되는 리소스이므로 커널 오브젝트가 생성될 것이고 함수호출이 끝나면

커널 오브젝트를 의미하는 핸들이 리턴될 것이다.

------------------------------------------------------------------------------------------
#include <windows.h>

HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // Security Descriptor
SIZE_T dwStackSize, // initial stack size
LPTHREAD_START_ROUTINE lpStartAddress, // thread function
LPVOID lpParameter, // thread argument
DWORD dwCreateionFlags, // thread identifier
LPDWORD lpThreadId // thread identifier
);

리턴 값 : 성공 시 생성된 커널오브젝트의 핸들, 실패 시 NULL 리턴


인자 값 :

○ lpThreadAttributes
생성하려는 쓰레드의 보안에 관련된 설정을 위해 필요한 옵션이다. 디폴트(Default)보안을
위해서 NULL 포인터를 전달한다.

○ dwStackSize
쓰레드를 생성하는 경우, 모든 메모리 공간은 스택 공간은 독립적으로 생성된다.
따라서 쓰레드, 생성 시 요구되는 스택의 크기를 인자로 전달한다. 0을 전달할 경우
디폴트로 설정되어 있는 스태그이 크기를 할당 받는다.

○ lpStartAddress
쓰레드에 의해 호출되는 함수의 포인터를 인자로 전달한다.

○ lpParameter
lpStartAddress 가 가리키는 함수 호출 시, 전달할 인자를 지정해 준다.

○ dwCreateionFlags
새로운 쓰레드 생성 이후에 바로 실행 가능한 상태가 되느냐, 아니면 대기 상태로 들어가느냐를
결정하는 요소이다. 그리 중요한 요소는 아니며 0을 전달할 경우 바로 실행 가능한 상태가 된다.
우리는 0을 전달하면 충분하다.

○ lpThreadId
쓰레드 생성 시 쓰레드의 ID가 리턴되는데, 이를 저장하기 위한 변수의 포인터이다.


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

복잡해 보이지만 우리가 실제로 신경써 줄 부분은 lpStartAddress 와 lpParameter 두 가지 뿐이며

나머지는 디폴트 값 (0 혹은 NULL 포인터)으로 전달하면 된다.

 

※ 쓰레드가 소멸되는 시점은 쓰레드에 의해서 처음 호출 된 함수가 리턴하는 시점이다. 따라서 함수가

끝나면서 임의의 상수를 리턴하게 되면 쓰레드는 소멸된다. 이러한 방법 이외에 ExitThread 함수를

사용하는 방법도 있찌지만, 리소스를 해제하는 과정을 반드시 거쳐야 하는 불편함이 따르다.

따라서 일반적으로 함수 리턴을 통한 종료를 더 선호한다. (호환성 문제를 고려해 보더라도 이 방법을

선소해야 한다.) 따라서 쓰레드의 종료에 대해서는 다른 언급을 하지 않겠다. )

 

(위의 방법은 안전하지 않은 쓰레드 생성(?) 이라고 한다. 그래서 가급적(?) 사용하지 말라고

했던걸로 기억난다.... 그러면 어떤 함수를 사용해서 쓰레드를 생성하냐느... 바로 아래에 나올

안전한 쓰레드 함수를 사용하는것이다.)

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


[ 멀티 쓰레드 기반의 프로그램 작서을 위한 환경 설정 ]

 

컴파일러 창에서 Alt + F7 을 누르면 프로젝트 셋팅이 나온다.

VC버전별로 조금씩은 다를 수 있으나.

런타임 라이브러리 (User run-time library ) 목록에서 멀티스레드 DLL옵션을 줘야 한다.

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


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


[ 쓰레드 안전한 C라이브러리 함수 사용하기 ]

 

역사적으로 처음 C 라이브러리를 만들 당시만 해도 멀티 쓰레드에 대한 부분은 고려하지 않았다.

(그 당시만 해도 멀티 쓰레드라는 개념 조차 낯설던 시절이었으므로). 즉 프로그램은 프로세스내에서

단일 쓰레드로만 동작을 한다고 생각하였다. 따라서 표준 C라이브러리 함수 중 일부는 멀티 쓰레드

프로그램 내에서 안전하지 않다. 안전하지 않다는 의미는 잘못된 연살 결과를 가져 온다는 뜻이다.

마이크로소프트는 이러한 문제를 해결하기 위해서 쓰레드 안전한 C/C++ 라이브러리를 제공하고 있다.

이 라이브러리와의 링크를 위해서 우리는 프로젝트의 설정을 변경하는 방법에 대해서도 알아보았다.

위에 [ 멀티 쓰레드 기반의 프로그램 작서을 위한 환경 설정 ] <-- 참고

이게 한 가지만 더 중의하면 된다. 그것은 쓰레드를 생성할 때 이전에 소개했던 CreateThread 함수대신

_beginthreadex 함수를 호출해서 쓰레드를 생성하는 것이다. 그렇다면 여러분은 얼마든지 안심하고

C/C++ 라이브러리에 존재하는 함수를 안전하게 호출할 수 있다.

_beginthreadex 함수는 멀티 쓰레드 기반의 C/C++ 라이브러리에 포함되어 있으므로

"unresolved external symbol" 이라는 에러 메시지를 보게 된다면 라이브러리 설정이 잘못 되어 있는

경우라고 의심해 볼 수 있다. _beginthreadex 함수를 살펴보자


------------------------- < _beginthreadex 함수 > -----------------------------------

#include <process.h>

unsigned long _beginthreadex(
void* security, // Security Descriptor
unsigned stack_size, // initial stack size
unsigned (*start_address)(void*), // thread function
void* arglist, // thread argument
unsigned initflag, // createion option
unsigned* thrdaddr // thread identifier
)

리턴 값 : 성공 시 생성된 소켓의 핸들, 실패 시 0 리턴

인자 : CreateThread 함수와 동일...

○ security
생성하려는 쓰레드의 보안에 관련된 설정을 위해 필요한 옵션이다. 디폴트(Default)보안을
위해서 NULL 포인터를 전달한다.

○ stack_size
쓰레드를 생성하는 경우, 모든 메모리 공간은 스택 공간은 독립적으로 생성된다.
따라서 쓰레드, 생성 시 요구되는 스택의 크기를 인자로 전달한다. 0을 전달할 경우
디폴트로 설정되어 있는 스태그이 크기를 할당 받는다.

○ start_address
쓰레드에 의해 호출되는 함수의 포인터를 인자로 전달한다.

○ arglist
lpStartAddress 가 가리키는 함수 호출 시, 전달할 인자를 지정해 준다.

○ initflag
새로운 쓰레드 생성 이후에 바로 실행 가능한 상태가 되느냐, 아니면 대기 상태로 들어가느냐를
결정하는 요소이다. 그리 중요한 요소는 아니며 0을 전달할 경우 바로 실행 가능한 상태가 된다.
우리는 0을 전달하면 충분하다.

○ thrdaddr
쓰레드 생성 시 쓰레드의 ID가 리턴되는데, 이를 저장하기 위한 변수의 포인터이다.
--------------------------------------------------------------------------------

CreateThread 함수와 비교해 보면, 일단 전달하는 인자의 수가 같다. 뿐만 아니라 각각의 인자가

지니는 의미도 완전히 동일하다. 물론 순서도 동일하다. 다만 이름이 틀리고 선언되어 있는 데이터

타입이 조금 다를 뿐이다. 따라서 CreateThread 함수를 _beginthreadex 함수로 변경할 때는

적절한 형 변환만 조금 해 주면 된다.

이제 쓰레드 생성예를 만들어보자..


--------------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <process.h>

unsigned int WINAPI ThreadFunction(void *arg);

void Error(const char *mes);

int main()
{
HANDLE hThread = NULL;
DWORD dwThreadID = NULL;

hThread = (HANDLE)_beginthreadex(NULL,0,ThreadFunction,NULL,0,(unsigned*)&dwThreadID);

if(hThread == 0) Error("_beginthreadex Error\n");

printf("생성된 쓰레드의 핸들 : %d\n",hThread);
printf("생성된 쓰레드의 ID : %d\n",dwThreadID);

Sleep(3000);

printf("main 함수 종료!!\n");

return 0;
}


unsigned int WINAPI ThreadFunction(void *arg)
{
for(int i=0; i<5; i++)
{
Sleep(2000);
printf("쓰레드 실행 중\n");
}

return 0;
}

void Error(const char *mes)
{
printf("%s\n",mes);
exit(0);
}
--------------------------------------------------------------------------------


[ 결과화면 ]


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

 

이 예제는 총 두 개의 쓰레드를 생성한다. 하나는 main 함수를 호출하는 메인 쓰레드, 또하나는

ThreadFunction 호출하는 쓰레드이다.

문제는 메인 쓰레드가 먼저 끝난다는 것이다.

실행 결과를 보면 메시지의 출력이 메인 쓰레드의 종료와 함께 끝이 난 것을 알 수 있다.

즉 메인 쓰레드의 종료는 프로세스의 종료와 관계가 있음을 보여 주는 예제가 된다.

그렇다면 어떻게 해야 하는가? 물론 다른 쓰레드가 종료될 때까지 메인 쓰레드가 기다려 주면 될것이다.

그것을 다음 장에서 확인해 보자.

 

 

 

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

 

[출처] http://jinsemin119.tistory.com/89

걍 그대로 긁어옴

 

2014/01/17 - [프로그래밍 언어/C++] - [Windows] 쓰레드(Thread) 생성&종료 함수