_beginthreadex和_endthreadex源码概览

本文解析了UCRT库中_beginthreadex与_endthreadex的内部机制,展示了它们如何通过Windows CreateThread接口创建和结束线程,并讨论了各自在线程生命周期中的作用。特别关注了线程参数的创建与处理,以及异常捕获和资源清理过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

_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

2、_beginthread, _beginthreadex | Microsoft Docs

3、_endthread, _endthreadex | Microsoft Docs

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值