【问题标题】:How address of a function will pass to the std::thread函数的地址如何传递给 std::thread
【发布时间】:2020-12-09 13:04:55
【问题描述】:

我正在调试一个使用std::thread 运行函数的多线程应用程序。调试时我到达以下代码。

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);
}

但我不明白函数的地址如何传递给CreateThread API。为什么使用thread_start&lt;_beginthreadex_proc_type, true&gt;,以及函数的地址将如何通过该语句计算以供线程运行?

【问题讨论】:

  • 函数地址和参数存放在parameter
  • “使用std::thread。虽然我看到线程代码,但我没有看到 std::thread。您想对该代码进行现代化改造以使用std::thread吗?
  • @Jarod42 看起来 OP 正在研究 std::thread 的实现
  • 你从哪里得到这个 src 代码?通过此代码线程开始从公共入口点thread_start&lt;_beginthreadex_proc_type, true&gt; 执行。此函数从parameter 获取用户定义的入口点并调用它
  • @RbMm 这是 MSVC 库代码。

标签: c++ multithreading winapi


【解决方案1】:

显示的代码(_beginthreadex 函数)是 VC++ CRT 的一部分(可以在例如
C:\Program Files (x86)\Windows Kits\10\Source\10.0.17763.0\ucrt\startup\thread.cpp 中找到)。

unique_thread_parameter 的实例是一个结构,其中包含线程 procedure 指针、context 参数以及线程和模块 HANDLEs:

// corecrt_internal.h
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;

// thread.cpp
using unique_thread_parameter = __crt_unique_heap_ptr<
    __acrt_thread_parameter,
    thread_parameter_free_policy>;

create_thread_parameter 创建这样一个实例:

static __acrt_thread_parameter* __cdecl create_thread_parameter(
    void* const procedure,
    void* const context
    ) throw()
{
    unique_thread_parameter parameter(_calloc_crt_t(__acrt_thread_parameter, 1).detach());
    if (!parameter)
    {
        return nullptr;
    }

    parameter.get()->_procedure = procedure;
    parameter.get()->_context   = context;

    // Attempt to bump the reference count of the module in which the user's
    // thread procedure is defined, to ensure that the module will stay loaded
    // as long as the thread is executing.  We will release this HMDOULE when
    // the thread procedure returns or _endthreadex is called.
    GetModuleHandleExW(
        GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
        reinterpret_cast<LPCWSTR>(procedure),
        &parameter.get()->_module_handle);

    return parameter.detach();
}

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_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);

        _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;
}

它本质上调用invoke_thread_procedure,它只是调用procedure,传入context

static __forceinline unsigned int invoke_thread_procedure(
    _beginthreadex_proc_type const procedure,
    void*                    const context
    ) throw()
{
    return procedure(context);
}

调用周围的代码会进行一些记账以保持 CRT 的一致性,例如 退出时自动清理线程 (_endthreadex)。

【讨论】:

    猜你喜欢
    • 2016-02-18
    • 2017-03-22
    • 2020-12-15
    • 1970-01-01
    • 2016-04-16
    • 1970-01-01
    • 1970-01-01
    • 2021-05-30
    • 1970-01-01
    相关资源
    最近更新 更多