6

所以,这就是我所说的:std 很复杂。

在 VS2013 中这个简单的程序会导致死锁。

#include <thread>
#include <windows.h>

void foo()
{
}

void initialize()
{
    std::thread t(foo);
}

BOOL APIENTRY DllMain(HMODULE, DWORD reason, LPVOID)
{
    switch (reason)
    {
    case DLL_PROCESS_ATTACH:
        initialize();
        break;
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

在 DLLMain 中创建线程是完全错误的吗?这不是真的。来自 Microsoft 的文档“创建 DLL 的最佳实践”:“如果不与其他线程同步,创建线程可以工作”。所以 CreateThread 有效, _beginthreadex 有效, boost::thread 有效,但 std::thread 无效。这是调用堆栈:

ntdll.dll!_NtWaitForSingleObject@12()
KernelBase.dll!_WaitForSingleObjectEx@12()
msvcr120d.dll!Concurrency::details::ExternalContextBase::Block() Line 151
msvcr120d.dll!Concurrency::Context::Block() Line 63
msvcr120d.dll!Concurrency::details::_Condition_variable::wait(Concurrency::critical_section & _Lck) Line 595
msvcp120d.dll!do_wait(_Cnd_internal_imp_t * * cond, _Mtx_internal_imp_t * * mtx, const xtime * target) Line 54
msvcp120d.dll!_Cnd_wait(_Cnd_internal_imp_t * * cond, _Mtx_internal_imp_t * * mtx) Line 81
msvcp120d.dll!std::_Cnd_waitX(_Cnd_internal_imp_t * * _Cnd, _Mtx_internal_imp_t * * _Mtx) Line 93
msvcp120d.dll!std::_Pad::_Launch(_Thrd_imp_t * _Thr) Line 73
mod.dll!std::_Launch<std::_Bind<1,void,void (__cdecl*const)(void)> >(_Thrd_imp_t * _Thr, std::_Bind<1,void,void (__cdecl*const)(void)> && _Tg) Line 206
mod.dll!std::thread::thread<void (__cdecl&)(void)>(void (void) * _Fx) Line 49
mod.dll!initialize() Line 17
mod.dll!DllMain(HINSTANCE__ * __formal, unsigned long reason, void * __formal) Line 33

好的,std::thread 将“与其他线程同步”。

但为什么 ?

我希望这在 VS2015 中再也不会发生,我还没有测试它。

4

4 回答 4

8

规范std::thread包含以下要求(N4527 §30.3.1.2[thread.thread.constr]/6):

同步:构造函数调用的完成与副本的调用开始同步f

f要在新创建的线程上执行的可调用实体在哪里。)

在新线程开始执行线程过程之前,构造函数std::thread不能返回。创建新线程时,在调用线程过程之前,会调用每个加载的 DLL 的入口点DLL_THREAD_ATTACH。为此,新线程必须获取加载程序锁。不幸的是,您现有的线程已经持有加载程序锁。

因此,你死锁:现有线程无法释放加载器锁,直到新线程开始执行线程过程,但新线程无法执行线程过程,直到它可以获得由现有线程持有的加载器锁。

请注意,文档明确建议不要从 DLL 入口点创建线程:

您永远不应该从内部执行以下任务DllMain: [...] 调用CreateThread。如果你不与其他线程同步,创建一个线程可以工作,但它是有风险的。

(该页面有一长串不应该从 DLL 入口点完成的事情;这只是其中之一。)

于 2015-08-28T14:02:34.770 回答
0

使用detach()成员函数修复崩溃。例子:

void Hook_Init();

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        {
            std::thread hookthread(Hook_Init);
            hookthread.detach();
            break;
        }
    }
    return TRUE;
}

void Hook_Init()
{
    // Code
}
于 2016-05-15T11:19:01.053 回答
0

您正在将平台级别与std级别混合。调用原始 winapi 函数CreateThread可以在DllMain. 但无法保证std::thread将如何与平台交互。众所周知,在 中做这样的事情是极其危险的DllMain,所以我完全不推荐。如果您坚持尝试,那么您将需要小心翼翼地直接调用winapi,避免std实施的后果。

至于“为什么”,这并不重要,但在调试器中快速查看之后,似乎 MSVC 实现与新线程握手以交换参数和资源。所以它需要同步才能知道资源何时被移交。似乎有道理。

于 2015-08-27T14:41:50.350 回答
0

std::thread创建一个 C++ 线程。这意味着您可以依赖该线程中的 C++ 库。这意味着必须设置某些共享数据结构,这会强制同步(您可能会并行创建多个线程)。堆栈跟踪清楚地表明了这一点:std::_Cnd_waitX显然是标准库的一部分,并且显然是同步的。同步在您提到的文档中被列入黑名单,因此这次崩溃并不令人意外。

再往上我们看到的堆栈Concurrency::这是特定于 VS2015 之前的 Visual Studio 版本。这意味着你可能会在 VS2015 中走运。执行线程同步DllMain并不能保证崩溃。很有可能。

于 2015-08-27T15:35:26.420 回答