_beginthreadex是ucrt提供的创建线程的接口,_beginthreadex在内部调用了windows系统提供的CreateThread接口,_endthreadex的作用是显式的结束线程,实际上线程结束的时候会自动调用这个接口。
ucrt源码中_beginthreadex和_endthreadex的实现在下面这个路径
C:\Program Files (x86)\Windows Kits\10\Source\10.0.10150.0\ucrt\startup\thread.cpp
一、_beginthreadex
_beginthreadex主要包括2个步骤,第一步创建线程参数,第二步调用系统接口创建线程。
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);
// 1 创建线程参数
unique_thread_parameter parameter(create_thread_parameter(procedure, context));
if (!parameter)
{
return 0;
}
// 2 调用系统接口创建线程
DWORD thread_id;
HANDLE const thread_handle = CreateThread(
reinterpret_cast<LPSECURITY_ATTRIBUTES>(security_descriptor),
stack_size,
thread_start<_beginthreadex_proc_type>,
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);
}
第一步调用create_thread_parameter创建线程参数
线程参数是如下所示的结构体,包括了线程的入口方法_procedure和方法的入参_context,从代码注释可以看到线程句柄_thread_handle只会在调用_beginthread创建线程的时候进行初始化,而调用_beginthreadex创建线程不会初始化.
这就是为什么调用_beginthread创建线程,当线程结束之后调用CloseHandle会导致0xC0000008错误(An invalid handle was specified),而使用_beginthreadex创建线程不会导致这个问题,后面在_endthreadex的源码中就可以看到详细原因。
typedef struct __acrt_thread_parameter
{
// The thread procedure and context argument
void* _procedure;
void* _context;
// The handle for the newly created thread. This is initialized only from
// _beginthread (not _beginthreadex). When a thread created via _beginthread
// exits, it frees this handle.
HANDLE _thread_handle;
// The handle for the module in which the user's thread procedure is defined.
// This may be null if the handle could not be obtained. This handle enables
// us to bump the reference count of the user's module, to ensure that the
// module will not be unloaded while the thread is executing. When the thread
// exits, it frees this handle.
HMODULE _module_handle;
// This flag is true if RoInitialized was called on the thread to initialize
// it into the MTA.
bool _initialized_apartment;
} __acrt_thread_parameter;
第二步是调用系统接口CreateThread创建线程,这里需要注意的是传入的方法入口并不是我们传递给_beginthreadex的入参procedure,而是对procedure进行了装饰,实际的方法入口变成了thread_start,thread_start是一个模板函数,代码如下
template <typename ThreadProcedure>
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_is_packaged_app())
{
context->_initialized_apartment = __acrt_RoInitialize(RO_INIT_MULTITHREADED) == S_OK;
}
__try
{
ThreadProcedure const procedure = reinterpret_cast<ThreadProcedure>(context->_procedure);
// 当线程方法结束之后就会自动调用_endthreadex
_endthreadex(invoke_thread_procedure(procedure, context->_context));
}
__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;
}
从上面的代码中可以看到thread_start会在我们传入的方法结束之后调用_endthreadex,实际上从代码中可以看到_beginthread的实现中也是通过thread_start装饰线程执行的函数,所以_beginthread在结束后也是调用的_endthreadex,而不是调用_endthread。实际上后面可以看到_endthreadex和_endthread的实现几乎是一样的,调用两个方法实际上是一样的。
另外thread_start内部使用SEH机制捕获异常。
_beginthread和_beginthreadex的入参不一样,但是内部实现是类似的,_beginthreadex可以指定新建线程的初始状态,而_beginthread不能指定状态,默认使用CREATE_SUSPENDED使得线程创建之后挂起,然后再通过ResumeThread启动。
二、_endthreadex
extern "C" void __cdecl _endthread()
{
return common_end_thread(0);
}
extern "C" void __cdecl _endthreadex(unsigned int const return_code)
{
return common_end_thread(return_code);
}
从上面可以看到_endthreadex和_endthread都调用了common_end_thread,只不过入参不同。由于_beginthreadex没有初始化线程参数中持有的线程句柄parameter->_thread_handle,因此在线程结束后自动调用_endthreadex的过程中,只有_beginthread创建的线程会执行到CloseHandle。
如果有DLL引用的话,通过FreeLibraryAndExitThread释放对DLL的引用并且退出线程,否则直接调用ExitThread。
static void __cdecl common_end_thread(unsigned int const return_code) throw()
{
__acrt_ptd* const ptd = __acrt_getptd_noexit();
if (!ptd)
{
ExitThread(return_code);
}
__acrt_thread_parameter* const parameter = ptd->_beginthread_context;
if (!parameter)
{
ExitThread(return_code);
}
if (parameter->_initialized_apartment)
{
__acrt_RoUninitialize();
}
if (parameter->_thread_handle != INVALID_HANDLE_VALUE && parameter->_thread_handle != nullptr)
{
// 只有_beginthread创建的线程能执行到这里
CloseHandle(parameter->_thread_handle);
}
if (parameter->_module_handle != INVALID_HANDLE_VALUE && parameter->_module_handle != nullptr)
{
FreeLibraryAndExitThread(parameter->_module_handle, return_code);
}
else
{
ExitThread(return_code);
}
}
Reference:
1、CreateThread function (processthreadsapi.h) - Win32 apps | Microsoft Docs