광고


Thread 생성/소멸에 관한 짤막한 정리 시스템



1. 생성


Thread가 생성될 때 프로세스의 code, data, heap data는 공유하고, thread의 stack 영역만 별도로 생성된다.
(링크 참고 : http://sweeper.egloos.com/2815395)


보통 CreateThread 등의 함수를 이용하여 thread를 생성할 때 thread의 스택 크기를 인자로 넘길 수 있는데, 이를 0 으로 넘기면
기본적으로 1 MB가 할당된다.


헌데, 이 스택 크기를 1메가 보다 적게 잡으면 어떻게 될까?
결론은 명시적으로 스택 크기를 1메가보다 작게 잡아도 OS가 알아서 1메가로 고정시켜 버린다.

즉, thread의 최소 스택 사이즈는 1메가 바이트이다.
(위 내용은 Win7 64bit, VS2k8에서도 마지막으로 테스트 해 보았다)


Thread를 생성할 때엔 CreateThread를 사용하지 않고  CRT 런타임 라이브러리 함수인 _beginthreadex() 함수를 사용하자!

_beginthreadex 함수도 내부적으로는 CreateThread 함수를 사용하지만, CreateThread에 앞서 독립적인 메모리 블록(_tiddata)을 CRT 런타입 힙에 할당한다.


할당한 이 메모리 블럭의 주스를 각 쓰레드의 TLS에 저장하여 연계시키며, 이는 각종 C/C++ 런타임 라이브러리 함수들의 멀티 쓰레드 문제를 해결하는데 사용된다.

즉, Multi-Threaded 런타임 라이브러리 함수 호출시 해당 쓰레드의 메모리 블록을 찾아보게 될 때 사용된다.

(이는 C/C++ 런타임 라이브러리 함수들이 애초에 멀티 쓰레드를 고려하지 않고 제작되었기 때문이다.)

(예를 들어, errno 전역 변수의 경우 각각의 메모리 블럭에 값을 고유하게 가지게 된다)


따라서, C/C++ 런타임 라이브러리 함수 사용시, 멀티 쓰레드 버그없이 thread 운용이 가능하다. 


Thread procedure가 return; 하게 되면 내부적으로 _endthreadex() 가 호출되어 독립적으로 할당되었던 메모리 블럭을 해제해 준다. 

_beginthreadex가 호출되면 바로 thread proc 함수를 호출하는 것이 아니라, _threadstartex가 호출된다.

_threadstartex는 내부적으로 _callthreadstartex를 호출하며, 이 녀석이 _endthreadex를 호출하게 된다.

_endthreadex는 _beginthreadex에서 생성했던 메모리 블럭을 해제한 이후 ExitThread를 호출한다.


참고로, _beginthreadex / _endthreadex는 #include <process.h> 선언이 필요하다.


덧붙이자면, 사실 CreateThread를 사용해도 C/C++ 런타임 라이브러리를 위한 메모리 블럭이 생성되긴 한다.

먼저 C/C++ 런타임 라이브러리 함수가 호출되었는데, 메모리 블럭이 NULL이면 해당 쓰레드에 메모리 블럭을 생성시켜주기 때문이다.

하지만, CreateThread로 쓰레드를 생성한 플머가 이를 닫을 때 _endthreadex로 닫으리라고는 생각치 않는다.

ExitThread로 닫는 순간 저 메모리 블럭을 해제되지 않을 것이다.

(물론 프로세스가 완전히 종료되면 사라지긴 한다만...)


Thread 종료 후에는 CloseHandle을 반드시 호출해야 한다.

Thread는 생성되자마자 UC(Usage count)가 2가 되므로,

(해당 쓰레드를 생성한 프로세스가 1, 쓰레드 스스로 1 합쳐서 2가 된다)

thread 종료시 1이 감소하고 CloseHandle로 UC를 하나 더 감소시켜야 0 이 되어 쓰레드 커널 오브젝트가 깨끗이 제거된다.


주의 : _beginthread/_endthread는 추천하지 않는다. 

일단 thread가 생성되지마자 내부적으로 CloseHandle이 호출되어 반환되는 thread handle도 유효하지 않다.


2. 종료(소멸)


Thread를 종료하는 방법에는 여러가지 방법이 있지만, 거의 뭐 하나 밖에 쓰이지 않는다.

그래도 이왕 정리한 김에 간단하게 아래에 목록을 정리하면...

  • 정상 종료, 그대의 이름은 return;
    : 이게 제일 좋다!
    1) 쓰레드 함수 내에서 생성한 모든 C/C++ 오브젝트들은 적절히 소멸된다.
    2) 쓰레드 스택으로 사용하였던 메모리를 반환한다.
    3) 쓰레드의 종료 코드를 쓰레드 함수의 반환 값으로 설정한다 (이 값은 쓰레드 커널 오브젝트에 저장된다)
    4) 쓰레드 커널 오브젝트의 usage count를 감소시킨다.
    5) _beginthreadex() 함수를 통해 생성된 쓰레드의 경우 return으로 _endthreadex가 자동 호출까지...

  • ExitThread
    : Thread Procedure 내부에서 원하는 시점에 종료가 가능하나...
    : But, 앞서 호출된 함수들이 잡아놓은 스택 프레임에 동적인 객체가 있으면...해제가 되지 않는다.
    : 미리 모두 해제하고 ExitThread를 호출해야 한다.
     
  • TerminateThread
    : 쓰레드 외부에서 강제로 킬 쓰레드 하는 녀석인데...
    : 다른 녀석이 갑자기 죽여버리니, 죽는 쓰레드에선 뭘 할수도 없고 걍 죽어야 된다. 쓰지 말자...
    : 가장 치명적인 문제는 쓰레드 스택을 정리하지 않는 점이다. (물론 해당 프로세스가 죽으면 모두 정리가 된다)
    : 또한, 이 메써드는 DLL에게 쓰레드 종료 통지를 전달하지도 못한다.


덧글

  • 취준생 2016/11/09 23:32 # 삭제 답글

    안녕하세요. 먼저, 좋은 글 올려주셔서 감사합니다.
    읽던 중 _beginthreadex() 설명에 관해 질문이 생겨 댓글을 달게 되었습니다.
    설명을 보면,
    _beginthreadex() -> _threadstartex() -> _callthreadstartex() -> _endthreadex() 를 호출한다고 되어있는데,

    _endthreadex()를 호출하면 쓰레드가 종료되는 것 아닌가요?
    아니면, _callthreadstartex()를 호출함으로써 생성된 Thread에서 return시에 _endthreadex()를 호출한다는 것인가요?
댓글 입력 영역