C++ std::线程导致DLLMain中死锁

C++ std::线程导致DLLMain中死锁,c++,visual-c++,msvc12,C++,Visual C++,Msvc12,这就是我要说的:性病是复杂的 在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_PROCES

这就是我要说的:性病是复杂的

在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;
}
#包括
#包括
void foo()
{
}
void initialize()
{
标准:螺纹t(foo);
}
BOOL APIENTRY DllMain(HMODULE、DWORD reason、LPVOID)
{
切换(原因)
{
案例DLL\u进程\u附加:
初始化();
打破
案例DLL\u线程\u连接:
打破
案例DLL\u线程\u分离:
打破
案例DLL\u进程\u分离:
打破
}
返回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
ntdll.dll_NtWaitForSingleObject@12()
KernelBase.dll_WaitForSingleObjectEx@12()
msvcr120d.dll!并发::详细信息::ExternalContextBase::Block()行151
msvcr120d.dll!并发::上下文::块()第63行
msvcr120d.dll!并发::详细信息::_条件_变量::等待(并发::关键_部分和_Lck)第595行
msvcp120d.dll!do_wait(_Cnd_internal_imp_t**cond,_Mtx_internal_imp_t**Mtx,const xtime*target)第54行
msvcp120d.dll_Cnd\U等待(\u Cnd\u内部导入**秒,\u Mtx\u内部导入**Mtx)第81行
msvcp120d.dll!标准:第93行
msvcp120d.dll!标准::_焊盘::_发射(_Thrd_imp_t*_Thr)线路73
mod.dll!标准::_启动(_Thrd_imp_t*_Thr,标准:_Bind&&u Tg)第206行
mod.dll!标准::螺纹::螺纹(空心(空心)*\U Fx)第49行
mod.dll!初始化()第17行
mod.dll!DllMain(HINSTANCE正式、未签字的长理由、无效正式)第33行
好的,std::线程将“与其他线程同步”

但是为什么呢


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

您将平台级别与
std
级别混为一谈。调用原始winapi函数
CreateThread
可以在
DllMain
中工作。但无法保证std::thread将如何与平台交互,所以我一点也不推荐。如果您坚持尝试,那么您将需要直接调用winapi,避免
std
实现的后果


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

<代码> STD::线程< /Cult>创建C++线程。这意味着你可以依赖于该线程中的C++库。这意味着必须设置某些共享数据结构,这将强制同步(您可以并行创建多个线程)。堆栈跟踪清楚地显示:

std::_Cnd_waitX
显然是标准库的一部分,并且正在同步。同步在您提到的文档中被列入黑名单,所以这次崩溃并不令人惊讶


进一步向上,我们可以看到
并发::
。这是特定于VisualStudio版本的。这意味着你可能会在VS2015中走运。在
DllMain
中执行线程同步不能保证崩溃。非常可能。

标准螺纹规范包含以下要求(N4527§30.3.1.2[螺纹.螺纹.施工]/6):

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

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

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

因此,死锁:现有线程在新线程开始执行线程过程之前无法释放加载程序锁,但新线程在获得现有线程持有的加载程序锁之前无法执行线程过程

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

决不能从
DllMain
中执行以下任务:[……]调用
CreateThread
。如果不与其他线程同步,则创建线程可以正常工作,但这是有风险的

(该页面有一长串不应从DLL入口点执行的操作;这只是其中之一。)

使用
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
}

并不是说性病很复杂。线程的MSFT实现似乎有缺陷。它试图表现得很好,并与实际开始运行的线程互锁。如果你有一个1-800的支持电话号码,并且不特别喜欢程序员的电话,而这些电话没有正确地捕捉到他们的lambda参数,你会倾向于这样做。使用调试器的Debug>Windows>Threads调试器窗口找出线程未启动的原因。但它也在尝试调用一个DllMain()入口点,这在某种程度上已成定局。加载程序锁上的死锁是一个标准错误。像“在DLLMain中创建线程”这样的事情并不危险,有时这是唯一的方法,因为DLLMain中的其他事情确实很危险。但看来std::thread不仅仅是关于创建线程。
std::thread
肯定只是关于创建线程。但这不仅仅是关于
CreateThread
。有时你无法区分“级别”。比方说