(Win32 : WindowsProgramming-17) Thread Basic

Posted by : at

Category : win32   WindowsProgramming


프로세스와 스레드

  • 프로세스
    • 가상의주소 공간 + 프로세스 커널 오브젝트(PKO)
    • 직접동작하진 않는다 정적인 존재
  • 스레드
    • 프로세스내에서 코드를 실행하는 실행흐름(동적인 존재)
    • 스레드역시 OS입장에선 관리의 대상이기에 TKO(Thread Kernel Object)가 존재
    • TKO내에 참조계수, 보안설정, ID 등이 관리된다.

Quantum Time

 A    B    Thred
 |    |
 | -> |
 |    |

위 그림과 같이 하나의 CPU에서 두 개의 Thread를 동작한다면 A Thread에서 특정 시간 후 B Thread로 점유권을 이전해야할 것이다. 이 때 이전하는 특정 시간을 Quantum Time이라 한다.
Thread를 이전하는 행위 자체는 Context Switch라 하며 이때 말하는 Context의 의미는 아래에 설명.

또한 Thread가 이전 시 직전에 사용 되던 Thread의 정보(실행주소, 스택주소 등)을 Kernel Object에 담고 있어야 하고, 이 정보를 Thread Kernel Object의 CONTEXT에 저장하며 저장되는 정보는

  • EIP 레지스터 정보 : 실행주소
  • ESP 레지스터 정보 : 스택주소

를 대표적으로 저장하게 된다.


Thread 생성

#include <stdio.h>
#include <Windows.h>
#include <tchar.h>

DWORD __stdcall foo(void* p)
{
    return 0;
}

int _tmain()
{
    DWORD tid;

    HANDLE handle = CreateThread(0, 0, foo, 0, 0, &tid);

    getchar();

    CloseHandle(handle);
}
HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES   lpThreadAttributes,       // Kernel Object의 보안속성
  SIZE_T                  dwStackSize,              // Kernel Object의 Stack Size(기본은 1M)
  LPTHREAD_START_ROUTINE  lpStartAddress,           // 실행 함수 주소
  __drv_aliasesMem LPVOID lpParameter,              // 매개변수
  DWORD                   dwCreationFlags,          // Thread 플래그
  LPDWORD                 lpThreadId                // Thread ID
);
  • 모든 Thread는 생성 시 참조 개수가 항상 2개다(자기자신, 부모 Thread)
  • 따라서 부모에서 CloseHandle해줘야 된다.
    • 만약 해주지 않으면 쓰레드가 남아있게 된다.

Thread의 문제점은?

#include <stdio.h>
#include <Windows.h>
#include <tchar.h>

DWORD __stdcall foo(void* p)
{
    // 여기서 C 표준 함수 사용시 문제가 발생할 수 있다.
    // 이런 문제의 해결을 위해 _beginthreadex를 쓰는것을 추천한다
    // 자세한설명은 이후에 _beginthreadex를 설명하며 하겠음
    return 0;
}

int _tmain()
{
    DWORD tid;

    HANDLE handle = CreateThread(0, 0, foo, 0, 0, &tid);

    getchar();

    CloseHandle(handle);
}

해결책?

#include <stdio.h>
#include <Windows.h>
#include <tchar.h>
#include <process.h>

// 순수 c의 자료형으로 리턴받는다
unsigned int __stdcall foo(void* p)
{
    // C함수를 사용하더라도 메모리 누수가 없다.
    return 0;
}

int _tmain()
{
    unsigned int tid;

    HANDLE handle = (HANDLE)_beginthreadex_(0, 0, foo, 0, 0, &tid);

    getchar();

    CloseHandle(handle);
}

Thread 종료

#include <stdio.h>
#include <Windows.h>
#include <process.h>

struct Test
{
    Test() { printf("Test()\n"); }
    ~Test() { printf("~Test()\n"); }
};

UINT __stdcall foo(void* p)
{
    Test t;

    _endthreadex(0);
    ExitThread(0);  // 이렇게 Thread를 종료할 수있다.
    // 단, 이렇게 종료시 Test의 소멸자가 호출되지 않음.

    return 0;
}

int main()
{
    HANDLE hThread  = (HANDLE)_beginthreadex(0, 0, foo, 0, 0, 0);

    TerminateThread(hTread, 200);   // 외부에서 호출가능, 
    // 이것도 역시 내부 오브젝트(Test)의 소멸자가 호출되지 않는다.

    getchar();
}
#include <stdio.h>
#include <Windows.h>
#include <process.h>

UINT __stdcall foo(void* p)
{
    _tprintf(_T("start foo\n"));

    Sleep(5000);

    _tprintf(_T("end foo\n"));
    return 0;
}

int main()
{
    HANDLE hThread  = (HANDLE)_beginthreadex(0, 0, foo, 0, 0, 0);

    return 0;   // 바로종료?
    // 주 스레드 main 함수가 return 될 시 프로세스 내 모든 스레드가 강제 종료 된다.

    ExitThread(0);  // 메인스레드만 죽인다, 생성된 스레드는 살아있음.
}

새로만든 Thread를 대기하게 변경해보자.

int main()
{
    HANDLE hThread  = (HANDLE)_beginthreadex(0, 0, foo, 0, 0, 0);

    DWORD ret = WaitForSingleObject(hThread, INFINITE);

    if(ret == WAIT_OBJECT_0)
    {
        DWORD code;
        GetExitCodeThread(hTread, &code);   // 종료코드를 받는다
    }
    else if (ret == WAIT_TIMEOUT)
    {

    }

    return 0;
}

About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links