[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이 아닌 엉뚱한 결과가 나올 가능성이 충분히 있는 코드라는 점이죠.

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

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

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


반응형

+ Recent posts