std :: thread导致DLLMain中的死锁

时间:2015-08-27 14:28:47

标签: c++ visual-c++ msvc12

所以,这就是我所说的: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中创建一个线程是完全错误的?事实并非如此。从 文档&#34;创建DLL的最佳实践&#34;微软: &#34; 如果您不与其他人同步,创建一个线程就可以了 线程&#34 ;.所以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将&#34;与其他线程同步&#34;。

但为什么?

我希望VS2015再也不会发生这种情况,我还没有测试过。

4 个答案:

答案 0 :(得分:6)

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

  

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

(其中f是要在新创建的线程上执行的可调用实体。)

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

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

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

  

您永远不应在DllMain内执行以下任务:[...]致电CreateThread。如果不与其他线程同步,则创建线程可以正常工作,但这样做有风险。

(该页面列出了一些不应该从DLL入口点完成的事情;这只是其中之一。)

答案 1 :(得分:0)

您将平台级别与std级别混合。调用原始winapi函数CreateThread可以在DllMain中使用。但是,std::thread与平台的互动方式并不能保证。 It's well known that it's extremely dangerous to be doing things like this in DllMain,所以我根本不推荐它。如果你坚持尝试,那么你需要小心翼翼地直接调用winapi,避免std实施的后果。

至于&#34;为什么&#34;,它不应该真的重要,但是在调试器中快速查看之后,似乎MSVC实现与新线程握手以交换参数和资源。因此,需要同步才能知道资源何时被切换。似乎是合理的。

答案 2 :(得分:0)

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

我们看到Concurrency::进一步向上堆叠。这特定于Visual Studio版本up to VS2015。这意味着您可能在VS2015中幸运。在DllMain中执行线程同步不是保证崩溃。很有可能。

答案 3 :(得分: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
}