JHB의 프로그래밍 삽질기

Thread 생성에 대한 간략한 코드 이것저것 모음 본문

PROGRAMMING/C C++ MFC

Thread 생성에 대한 간략한 코드 이것저것 모음

roter 2009.02.10 13:41
#include <stdio.h>
#include <windows.h>
#include <process.h> /*for _beginthreadex, _endthreadex*/
#pragma comment( lib, "ws2_32" )


unsigned WINAPI ThreadFunction(void* arg);

int main(int argc, char** argv)
{
 HANDLE hThread;
 DWORD dwThreadID;

 hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunction, NULL, 0, (unsigned*)&dwThreadID);
 if(hThread == 0)
 {
  puts("_beginthreadex() error");
  exit(1);
 }

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

 printf("main 끝\n");

 return 0;
}

unsigned WINAPI ThreadFunction(void* arg)
{
 int i=0;
 for(i=0; i<5; i++)
 {
  Sleep(1000);
  printf("%d\n",i);
 }

 return 0;
}

어디서 퍼온 내용을 조금 수정했는데 어디서 퍼왔는지 모르겠습니다.. 혹시 출처를 아는 분은 말씀해주시면 감사하겠습니다..





_beginThread() 와 _beginThreadex() 함수 성질급한D형개발자

2008/06/04 00:26

http://blog.naver.com/drags99/150032023121

두 함수는 모두 스래드를 생성할 때 호출하는 함수이다.

이 두 함수는 겉으로 보기엔 동일한 기능을 하지만 차이점이 있어서 이를 알고 넘어가야 한다.

MSDN을 보면 _beginthread _beginthreadex 를 사용하면 스래드에 여러 인수를 전달할 수 있습니다. 라고 되어있다. 이러한 이유로 CreateThread 보다는 위 두 함수를 많이 이용한다.

위 두 함수 중에도 개인적으로는 _beginthreadex 함수를 주로 이용하고 있다.

위 두 함수의 차이점을 비교해 보면

함수 호출 규약이 _beginthread 함수는 __cdecl 이고, _beginthreadex 함수는 __stdcall 이다.

함수 호출 규약에 따라 내부적으로 인자 값들을 스택에 저장할 때 순서가 달라진다. 이러한 부분은 코딩을 하는 부분에서는 크게 신경 쓰지 않아도 되는 부분이다.


또 다른 차이점은 각각의 함수는 _endthread, __endthreadex 라는 함수와 같이 이용된다는 점이다. 딱 봐도 뒤에 ex 가 붙은 함수와 안 붙은 함수가 세트로 사용된다는 걸 쉽게 이해할 수 있을 것이다.


또 다른 차이점은 return Value 값이다.

_beginthread 함수의 경우 성공일 때 0, 실패일 때 -1을 리턴 하지만, _beginthreadex 함수는 실패일 때 0(NULL), 성공일 때 -1이 아닌 값을 리턴 한다.

_beginthread 함수는 성공과 실패를 구분하지만 _beginthreadex 함수는 실제 HANDLE 을 리턴하기 때문에 GetExitCodeThread 함수 호출 등이 가능하게 된다.

두 함수를 호출하는데 있어서 인자 값도 차이가 난다.

_beginthreadex 함수에서 initflag 값을 이용해 스래드가 생성되는 시점에서 스래드의 상태를 설정할 수 있다.

threadaddr 32bit 포인터 값으로 스래드의 주소 값을 반환 받을 수 있다.

Security 인자를 이용해 자식 프로세스에서 이 함수에서 반환된 스래드의 핸들을 상속받아서 사용할 수 있는지 유무를 결정할 수 있다.

위와 같은 차이가 나지만 대부분은 _beginthreadex 함수를 많이 이용할 것이다.

 

하지만 _beginthreadex 함수를 이용해야 하는 아주 결정적인 이유가 하나 더 있다.

Ansi 표준 C 라이브러리 함수를 호출할 경우 _beginthread 함수를 이용해 생성된 스래드 에서는 동기화 이유 때문에 안정성을 보장받지 못하게 되지만 _beginthreadex 함수를 이용할 경우 스래드별로 내부적으로 별도의 메모리 공간을 할당하기 때문에 동기화와 관련돼 안정성을 보장받을 수 있게 된다. 이렇게 스래드 생성시 할당된 별도의 메모리 공간을 해제하기 위해서 _endthreadex 함수와 짝을 이뤄서 이용하게 되는 것이다.

컴파일러에 따라 다르겠지만 .Net 에서는 멀티스래드에 안정적인 라이브러리를 제공하고 있기 때문에 별도의 선언이나 조작 없이도 안정적인 라이브러리를 이용할 수 있다고 한다.(아직까지는 별 문제 없이 잘 쓰고 있다.)

만약 _beginthreadex 함수로 스래드를 생성하고 _endthread 함수를 이용해 스래드를 종료하게 된다면 스래드 생성시에 할당된 메모리가 반환되지 않기 때문에 이렇게 스래드를 이용할 수 없다.

하지만 스래드를 생성할 때 인자로 넘긴 함수 포인터에서 return 을 이용해 스래드를 종료하게 되면 별도의 _endthreadex 함수를 호출하지 않아도 생성시에 할당된 메모리가 반환되기 때문에 return 을 이용하면 이 부분은 별로 걱정할 필요 없다.

스래드 종료 시 반환된 값은 내부에 생성된 스래드 커널에 저장되고, GetExitCodeThread 함수를 이용해 받아올 수 있다.

하지만 위 함수를 호출하기 전에 CloseHandle 함수를 이용해 스래드 핸들을 종료시킨다면 내부에 생성된 스래드 커널이 삭제되기 때문에 반환 값을 받을 수 없게 된다.

스래드의 반환 값을 이용할 필요가 있을 경우 GetExitCodeThread 함수를 이용해서 반환 값을 받아온 후에 CloseHandle 함수를 호출해 줘야 한다.

추가로 한가지 더 얘기 하면 CloseHandle 을 이용해서 함수 호출 시 반환된 스래드의 핸들을 종료시키게 되면 내부적으로 스래드 커널에 UC가 줄어들게 되 스래드가 종료되는 시점에서 스래드 커널도 바로 종료될 수 있다. 이 부분은 커널과 핸들러에 관한 내용을 미리 알고 있어야 해서 간단히 설명한다.

 

.. 두 함수를 비교하다 보니 결론적으로 코딩할때는 _beginthreadex 함수만 이용하게 된다.

솔직히 _endthreadex 함수도 거의 사용하지 않는다. 그냥 함수 내에서 return 을 이용해 스래드를 종료하고 있다.





[Win API] 스레드 강좌 + CreateThread() 와 _beginthreadex() 함수의 차이 ★ 윈도우 Prog

2008/07/16 16:38

http://blog.naver.com/kaltznnnyyy/140053695650

안녕하세요.

 

정말 오랫만의 포스팅입니다.

 

요새 이것저것 노느라 하느라 바빠서 공부와는 거리를 멀리하다가

 

다시 마음을 살짝 먹고, 글을 쓰고자 이렇게 달려왔습니다

(..라고 해봤자 계속 컴퓨터 앞입니다.)

 

이번에는 멀티스레드 관련해서 하나 이야기해보고 같이 실험을 해볼까합니다.

 

 

 

기본적으로 프로그램을 실행하게 되면 적어도 1개의 프로세스와 1개의 스레드를

생성하게 되는데, 그 1개의 스레드는 우리가 흔히 알고 있고 작성하고 있는

메인 스레드 ( Console일 경우 main() 함수로 호출 , Win32일 경우 WinMain() 함수로 호출 )

입니다. 메인 함수를 호출하게 되면 시스템 함수에 의해서 한개의 메인 스레드가 생성되는 것이죠.

 

멀티 스레드란 , 한 프로세스 내에서 메인스레드를 제외한 다른 스레드가 생성되고

돌아가고 있는 것을 말합니다. 한 프로세스 내에서 작업을 나눠서 할 수 있도록 도와주는

녀석이라고나 할까요. 그렇다고 절대 '동시'에 실행되는 것은 아닙니다. 엄청나게 빠르게

스레드 전환 이란 작업이 일어나고 있는 것 뿐이죠. 물론 프로세스도 동시에 실행되는 것이

아니라 프로세스간의 컨텍스트 스위칭 이란 작업이 빠르게 이루어지고 있는 것입니다. 이 작업이 프로그램을 동시에 돌아가도록 착각하게 만드는 것이죠.

 

멀티 스레드 관련이나 네트워크 관련이 아니고서는 대부분의 C, C++ 강좌 서적에는

이러한 부분에 대하여 자세하게 기술되어있지 않지요. ( 뭐 해봤자 머리만 아플테니... )

하지만 멀티스레드를 조금이라도 정확히 알고 싶으신 분이라면 한번쯤 이 글을 읽어보시는 것도

도움이 되리라 생각되는군요.

 

자 그러면 일단 스레드라는 녀석을 가볍게 살펴보도록 하겠습니다.

 

 

 

 

위 그림을 보시면 g_iData 라는 전역변수가 존재합니다.

저는 저 전역변수를 반복분을 통해서 증가시키면서 출력을 시키려고 합니다.

단, 멀티스레드를 사용하기로 하였으므로 증가를 시킬때 두 스레드로 나누어서

증가를 시켜보도록 하겠습니다.

( 뭐 멀티스레드의 장점을 설명하기에 적당한 예제는 아님을 미리 알려드립니다.^^

가장 이해하기 쉽도록 최대한 단순하게 만들었습니다.)

 

메인 스레드 이외에 다른 2개의 스레드를 생성하고 싶으면 CreateThread란 함수를 사용합니다.

일단 CreateThread 함수의 스펙을 살펴보도록 하죠. 

 


첫 번째 인자는 보안 관련 인자인데, 특별한 처리가 아닌이상 NULL로 지정하시면 됩니다.

두번째는 스레드에 할당할 스택 사이즈인데 0을 인자로 넘기면 디폴트 사이즈가 지정됩니다.

중요한건 세번째 , 네번째 인자인데,

세번째 인자는 스레드 함수의 주소(포인터)를 넘겨줍니다. 함수의 주소는 그 함수의 이름과 같은 건 알고 계시다고 가정하겠습니다. 그런데 그 함수의 기본 모형은 아무렇게나 주어도 되는것인가?

절대 아닙니다. 이 스레드는 유저가 관리하는 영역이 아닌, 시스템이 관리하는 영역이기 때문에

정해진 함수의 원형에 맞게 작성을 해야 시스템이 올바르게 읽고 판단합니다.

 

넘겨야하는 함수의 원형은 아래와 같습니다.

 

DWORD WINAPI ThreadProc(LPVOID lpParameter);

 

인자는 LPVOID 타입인데 , LPVOID는 다시 풀어쓰면 Long Pointer void (void *) 형입니다.

그러므로 4byte 주소를 넘겨줄 수 있는 것이죠. 리턴으로 넘겨주는 DWORD 형은 종료코드이구요.

그러면 질문이 생길 수도 있습니다.

 

"헉! 거대한 프로그램에서 인자 하나 가지고 어떻게 여러개의 변수를 처리하나요?

 스레드 이거 너무 비효율적 아닌가요?"

 

라고 말이죠. 하지만 조금만 생각을 달리하면 아주 쉽게 응용이 가능합니다. 나중에 아래에서 다시 설명을 드리겠습니다.

 

네번째 인자는 바로 위 스레드 함수에 넘겨야할 인자를 적어주시면 됩니다.

 

다섯번째 인자도 그냥 비워두시면 되구요 ( 무조건은 아니지만 지금 단계에서는 필요 없어요 )

여섯번째 인자에는 스레드의 ID 번호를 저장할 DWORD 자료형의 주소를 넘깁니다.

이 스레드 ID번호는 다른 프로세스간 통신 등을 할 때 필요합니다.

( 이 함수가 넘겨주는 HANDLE 은 동일 프로세스 내에서만 유효합니다. )

 

이 함수를 호출하게 되면 이 스레드를 제어할 수 있는 HANDLE을 리턴하게 되는데,

스레드가 제대로 생성되지 않았을 경우에는 NULL을 리턴하게 됩니다.

 

자, 말이 길어졌는데 그럼 위 설명을 토대로 간단한 멀티스레드 프로그램을 작성하도록 하겠습니다.

 

 

위와 같이 작성을 하게 되면 어떠한 결과가 나올까요? 예상해보시고 아래 답을 봐주세요.

 

생각과는 다르게 처참한 결과라는 생각이 드시는 분들도 있으실 겁니다.

까무잡잡한 화면에 아무것도 출력되지 않다니요!! 헝헝헝..ㅠ

 

이런 결과가 생기는 이유는 "메인 스레드는 추가로 생성된 스레드의 종료를 기다리지 않는다"는

것에 있습니다. 다시 말해 ThreadFunc 함수가 호출되어서 실행이 되려는 순간 메인 스레드에서는 할일이 전혀 없었으므로 바로 끝나게 되는데, 메인스레드가 끝나면 모든 프로그램을 종료가 되기 때문에

ThreadFunc 함수 내부의 구문은 실행되지 못한 채 종료가 되어버리는 것입니다.

 

얼래... 그러면 메인에서 추가된 스레드가 돌아가도록 한 1초정도만 기다리게 해보는건 어떨까요?

CreateThread 함수를 호출한 다음에 Sleep(1000); 을 적고 다시 실행시켜보도록 하죠.

 

그러면 아래와 같은 정답 비스무리한 결과(?!!)가 나오게 됩니다.

 

 

오오... 이제 제대로 돌아가는 군요.... 라고 안심하면 절!대! 안됩니다 ㅎㅎ

 

일단 첫번째 문제점은 호출한 스레드가 처리하는 동작이 몇초가 걸리는지 몇분이 걸리는지,

아니면 지속적으로 유지가 되는지 전~~혀 알 길이 없기때문입니다. 위 코드는

for문 30번 도는 일이 1초안에는 끝난다고 확신했기 때문에 가능했던 것이지요.

 

두번째 문제점은 중간에 Sleep으로 오랜시간 스레드를 잠재우면

도스 프로그램 같은 경우에는 해당이 안되지만 윈도우 환경에서 다수의 프로그램이

동시에 돌아갈 때, 다른 프로그램으로의 컨텍스트 스위칭이 이루어지지 않게되는 문제가

생겨버리는 것이지요.

 

흠...그렇다면 방법이 없는것인가?! ㅎㅎ 아닙니다. 아주아주 친절하게

적절한(김대기...죄송;ㅅ;) 함수를 제공합니다.  바로!!

 

 

위 함수의 첫번째 인자에 스레드의 핸들을 넘겨주고 , 두번째 인자에 대기 시간(밀리초)을 넘겨주면

그 스레드가 종료될 때까지 대기를 하게 됩니다. 그럼 이 함수를 적용한 코드를 보도록 하죠.^^

 

 

Sleep 함수 대신에 WaitForSingleObject() 함수를 호출합니다. 두번째 인자에 넘겨준

INFINITE는 0xffffffff 로 정의 되어있는 정의문으로 , 스레드가 끝날때까지 무조건 계~~속

기다린다는 의미입니다. ThreadFunc 함수가 종료코드를 반환하기 전까지는 그 다음줄을

실행하는 것을 절대 허락하지 않는것이죠.

자 이제 다시 돌려보겠습니다.

 

 

오호! 이것이 원하는 결과입니다! 뭔가 코드가 깔끔하네요.

자 그럼 스레드 끝!

 

.

.

.

 

이라고 하면 말도 안되겠죠 ㅋㄷ 이제 시작입니다.

지금까지는 g_iData 변수를 증가시키는 스레드가 1개였죠? 이제 그 작업을 2스레드가

나눠서 해보겠습니다.

 

위 소스코드를 아래와 같이 적당히 바꿔보지요-

 

 

어떤 결과가 나올 것 같나요?^ㅅ^

쉽게 예상하실 수 있겠죠??

그럼 예상해보시고 밑의 결과를 봐주세요~

 

 

 

 

 

 

 

 

 

뭔가 콰지직 쿵! 하는 느낌 안드세요?! 헉헉헉...

저..절대 원하는 결과가 아닙니다. (위 결과는 여러 상황에 따라서 바뀌게 됩니다.)

1부터 30까지 차례대로 출력해주었으면 했다구요!!!그렇죠?

 

위와 같은 결과는 두 스레드가 동시에 콘솔창에 출력을 행하려고 하기 때문에 일어나게 됩니다.

(위에서도 말했지만 엄밀히 말하면 동시는 아닙니다).

사실 문제는 이 뿐만이 아닙니다. 위 코드는 g_iData에 접근하고 증가시키는데 걸리는 시간이

아주 짧기 때문에 두 스레드간의 처리가 우연히 겹치지 않은 것 뿐이고,

컴퓨터가 아주 느리다면 30이 아닌 엉뚱한 결과가 나올 가능성이 충분히 있는 코드라는 점이죠.

 

이러한 문제를 해결하기 위해서는 스레드 간에 동기화를 해주어야 합니다.

멀티 스레드 프로그램을 작성하기 위해서는 반드시 반드시 꼭 꼭 알아야할 개념이죠.

 

(다시 부족전쟁 하고 나서 이어씁니다)



0 Comments
댓글쓰기 폼