CreateThread와 _beginthreadex 간단하게 알아보기
https://basaeng.tistory.com/70
[WINDOWS VIA C/C++] 06.스레드의 기본
https://www.hanbit.co.kr/store/books/look.php?p_code=B2974835990 제프리 리처의 Windows via C/C++(복간판)이 책은 윈도우 XP, 윈도우 비스타, 윈도우 서버 2008까지 내용을 포괄한다. 이미 윈도우 10이 출시된 지 오래
basaeng.tistory.com
해당 포스트에서는 간단하게만 언급한 주제인데 복습겸 더욱 알아볼겸 포스팅해보겠습니다.
(내용은 제프리 리처의 WINDOWS VIA C/C++을 참고하였습니다.
CRT(C RunTime Library)
C/C++프로그램은 CRT에 의존합니다.

CRT는 언어차원에서 사용하는 구현체입니다.
예전에는 특정 함수들이 multithread-safe하지 않아 multithread환경에서는 사용할 수 없는 경우도 있었지만, 이제는 multithread-safe한 함수들이 표준(혹은 대체함수 사용)으로 사용되고 있습니다.
다만 이러한 함수들은 TLS 등 스레드마다 가질 수 있는 환경을 사용하기 때문에 thread가 생성되면 CRT의 관련된 데이터들에 대한 초기화가 필요합니다.
CreateThread의 문제점
CreateThread는 Windows API함수입니다.
thread를 만들고 이후에 CRT 함수를 사용하기 위해서는 CRT초기화가 필요하지만 API차원에서는 CRT를 알 수 없기 때문에 CreateThread 만으로는 새롭게 만들어진 Thread 내에서 CRT를 사용할 수 없습니다.
물론 CRT를 아예 사용하지 않는다면 상관이 없을 수도 있지만 그런 경우는 애초에 거의 없고 함수를 통일하는 것이 개발에도 편할 것이기 때문에 CreateThread대신 _beginthreadex를 사용해야 합니다.
https://learn.microsoft.com/en-us/cpp/error-messages/tool-errors/c-runtime-error-r6016?view=msvc-170
C Runtime Error R6016
Learn more about: C Runtime Error R6016
learn.microsoft.com
https://learn.microsoft.com/en-us/windows/win32/procthread/creating-threads
Creating Threads - Win32 apps
Review how to use the CreateThread function to create a new thread for a process. Examine a code example that shows its usage.
learn.microsoft.com
여러 공식 문서에서도 CreateThread 대신 _beginthreadex를 사용할 것을 권장하고 있습니다.
_beginthreadex
_beginthread함수도 존재하지만 레거시 함수이고 다른 기능들과 호환을 위해 사용하지 않습니다.
_beginthreadex는 CRT를 초기화하며 스레드를 생성하는 함수입니다.
thread.cpp에 존재하는 _beginthreadex의 함수를 먼저 보고 차례차례 알아보겠습니다.
extern "C" uintptr_t __cdecl _beginthreadex(
void* const security_descriptor,
unsigned int const stack_size,
_beginthreadex_proc_type const procedure,
void* const context,
unsigned int const creation_flags,
unsigned int* const thread_id_result
)
{
_VALIDATE_RETURN(procedure != nullptr, EINVAL, 0);
unique_thread_parameter parameter(create_thread_parameter(procedure, context));
if (!parameter)
{
return 0;
}
DWORD thread_id;
HANDLE const thread_handle = CreateThread(
reinterpret_cast<LPSECURITY_ATTRIBUTES>(security_descriptor),
stack_size,
thread_start<_beginthreadex_proc_type, true>,
parameter.get(),
creation_flags,
&thread_id);
if (!thread_handle)
{
__acrt_errno_map_os_error(GetLastError());
return 0;
}
if (thread_id_result)
{
*thread_id_result = thread_id;
}
// If we successfully created the thread, the thread now owns its parameter:
parameter.detach();
return reinterpret_cast<uintptr_t>(thread_handle);
}
security_descriptor: 다른 함수들과 비슷하게 SECURITY_ATTRIBUTES를 사용해 보안 속성을 지정합니다.
stack_size: 스택의 크기를 바이트단위로 지정합니다. 0을 넣는다면 기본 스레드 스택 크기를 확보합니다.
procedure: 스레드 진입 함수에 대한 함수 포인터입니다.
context: procedure에 사용되는 파라미터 포인터입니다.
creation_flags: 스레드 생성동작을 제어하는 플래그입니다.
thread_id_result: 생성되는 스레드의 tid를 받기 위한 포인터입니다.
먼저 시작 함수와 파라미터의 유효성에 대해 체크합니다.
이후 CreateThread를 호출합니다.
thread_start
CreateThread는 thread를 실행하면 시작할 함수와 파라미터를 넘깁니다.
_beginthreadex에서는 시작 함수로 thread_start를 지정하고 파라미터로는 실제 시작함수와 파라미터를 묶은 구조체를 넘겨 사용합니다.
template <typename ThreadProcedure, bool Ex>
static unsigned long WINAPI thread_start(void* const parameter) throw()
{
if (!parameter)
{
ExitThread(GetLastError());
}
__acrt_thread_parameter* const context = static_cast<__acrt_thread_parameter*>(parameter);
__acrt_getptd()->_beginthread_context = context;
if (__acrt_get_begin_thread_init_policy() == begin_thread_init_policy_ro_initialize)
{
context->_initialized_apartment = __acrt_RoInitialize(RO_INIT_MULTITHREADED) == S_OK;
}
__try
{
ThreadProcedure const procedure = reinterpret_cast<ThreadProcedure>(context->_procedure);
if constexpr (Ex)
{
_endthreadex(procedure(context->_context));
}
else
{
procedure(context->_context);
_endthreadex(0);
}
}
__except (_seh_filter_exe(GetExceptionCode(), GetExceptionInformation()))
{
// Execution should never reach here:
_exit(GetExceptionCode());
}
// This return statement will never be reached. All execution paths result
// in the thread or process exiting.
return 0;
}
__acrt_thread_parameter*로 캐스팅한 뒤 _procedure(entry function)과 _context(parameter)를 사용하는 것을 알 수 있습니다.
__acrt_getptd()->_beginthread_context에서 crt초기화 작업을 실행합니다.
이후 procedure(context->_context)를 통해 실제 스레드 시작함수를 실행합니다.
스레드가 반환된 이후에는 _endthreadex를 통해 스레드를 반환하며, crt할당에 대한 해제도 내부적으로 해주고 있음을 알 수 있습니다.
std::thread의 시작 함수 _Start의 내부에서도 _beginthreadex를 사용합니다.
template <class _Fn, class... _Args>
void _Start(_Fn&& _Fx, _Args&&... _Ax) {
using _Tuple = tuple<decay_t<_Fn>, decay_t<_Args>...>;
auto _Decay_copied = _STD make_unique<_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);
constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof...(_Args)>{});
_Thr._Hnd =
reinterpret_cast<void*>(_CSTD _beginthreadex(nullptr, 0, _Invoker_proc, _Decay_copied.get(), 0, &_Thr._Id));
if (_Thr._Hnd) { // ownership transferred to the thread
(void) _Decay_copied.release();
} else { // failed to start thread
_Thr._Id = 0;
_Throw_Cpp_error(_RESOURCE_UNAVAILABLE_TRY_AGAIN);
}
}
결론
_beginthreadex를 사용하자 + std::thread()도 내부적으로 _beginthreadex를 사용한다.