Computer Science 기본 지식/운영체제

[C++ Thread] Windows API에서 쓰레드 생성

로파이 2021. 6. 1. 01:38

Windows에서 사용할 수 있는 쓰레드를 알아본다.

 

1. CreateThread

CreateThread를 이용하여 쓰레드를 생성할 수 있다.

HANDLE WINAPI CreateThread(
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
    _In_ SIZE_T dwStackSize,
    _In_ LPTHREAD_START_ROUTINE lpStartAddress,
    _In_opt_ __drv_aliasesMem LPVOID lpParameter,
    _In_ DWORD dwCreationFlags,
    _Out_opt_ LPDWORD lpThreadId
    );
  • lpThreadAttributes : 보안 속성을 결정한다.
  • dwStackSize : 쓰레드 생성시 할당되는 스택 사이즈를 결정한다.
  • lpStartAddress : 쓰레드가 실행하는 실제 함수에 대한 주소를 의미한다.
  • LPTHREAD_START_LOUTINE 타입은 DWORD (WINAPI *PTHREAD_START_ROUTINE) (LPVOID lphreadParameter)를 의미하고 DWORD를 반환하고 void* 입력 매개변수를 갖는 함수이다.
  • lpParameter : 쓰레드 함수에 전달할 인자.
  • dwCreationFlags : 쓰레드 생성이후 행동을 결정할 수 있다. CREATE_SUSPENDED 인자를 전달하면 block 상태에서 스레드가 생성된다.
  • lpThreadId: 생성되는 쓰레드의 ID를 얻기위해 주소를 전달한다.

반환되는 인자는 쓰레드와 연결된 커널 오브젝트의 핸들이며 반드시 생성하는 쓰레드에서 모든 추가 쓰레드에 대한 핸들을 닫아야한다. (리소스 해제)

  • CloseHandle(Handle handle)

각 커널 오브젝트는 운영체제에 의해 관리되고 자원 사용 카운트 USE COUNT를 기록한다. USE COUNT는 쓰레드 생성시 함수를 실행하는 쓰레드에 연결된 핸들과 생성시 반환된 핸들에 의해 참조되므로 2를 가진다. 함수 실행 종료후 카운트는 1이되는데 생성 쓰레드에서 CloseHandle를 호출하지 않으면 USE COUNT가 1로 유지되어 쓰레드 생성시 할당한 리소스가 해제되지 않는다.

 

간단한 CreateThread 예제

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

#define NUM_THREADS 16

UINT count = 0;

DWORD WINAPI ThreadProc(LPVOID lpParam)
{
	for (UINT i = 0; i < 10000; ++i)
	{
		++count;
	}
	return 0;
}

int _tmain(int argc, TCHAR* argv[])
{
	DWORD dwThreadId[NUM_THREADS];
	HANDLE hThread[NUM_THREADS];

	for(UINT i = 0; i < NUM_THREADS; ++i)
	{
		hThread[i] = CreateThread(
                    nullptr,		// 기본 보안 속성
                    0,				// 기본 스택 사이즈 (1MB : 최소 사이즈)
                    ThreadProc,		// 쓰레드 함수
                    nullptr,		// 쓰레드 함수의 매개변수를 위한 인자
                    0,				// 디폴트 생성 flag
                    &dwThreadId[i]  // 쓰레드 ID를 전달받기 위한 인자
                    );
			
	}

	// 모든 쓰레드 실행이 완료될 때까지 대기한다.
	WaitForMultipleObjects(NUM_THREADS, hThread, TRUE, INFINITE);

	_tprintf(_T("Count : %d\n"), count);

	for (UINT i = 0; i < NUM_THREADS; ++i)
	{
		CloseHandle(hThread[i]);
	}
	return 0;
}

 WaitForMultipleObjects: 모든 쓰레드가 실행완료될 때까지 기다리는 배리어 역할을 한다.

 

2. _beginthreadex

_ACRTIMP uintptr_t __cdecl _beginthreadex(
    _In_opt_  void*                    _Security,
    _In_      unsigned                 _StackSize,
    _In_      _beginthreadex_proc_type _StartAddress,
    _In_opt_  void*                    _ArgList,
    _In_      unsigned                 _InitFlag,
    _Out_opt_ unsigned*                _ThrdAddr
    );
  • 매개 변수는 CreateThread와 같다.
  • _beginthreadex_proc_type은 unsigned int (LPVOID lpParam)이기 때문에 unsigned int를 반환해야한다.
for (UINT i = 0; i < NUM_THREADS; ++i)
{
	hThread[i] = (HANDLE)_beginthreadex(nullptr, 0, ThreadProc, nullptr, 0, &dwThreadId[i]);
}

 

CreateThread와 _beginthreadex 차이점

_beginthreadex는 멀티 쓰레드 기반 프로그래밍을 위한 Thread-Safe C 런타임 라이브러리에 포함된 함수이다. 멀티 스레드가 접근할 수 있는 코드 섹션인 경우 새로운 쓰레드를 생성할 때 할당하는 메모리 영역을 안전하게 확보하는 등 멀티 스레드에 안전한 스레드 생성을 제공한다.