std::thread 在 DLLMain 中导致死锁
std::thread cause deadlock in DLLMain
所以,这就是我所说的: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 中创建线程是完全错误的吗?这不是真的。从
微软文档"Best Practices for Creating DLLs":
"如果您不与其他线程同步,则创建一个线程可以工作
threads”。所以 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 会 "synchronize with other threads"。
但是为什么?
我希望在 VS2015 中不再发生这种情况,我还没有测试它。
您将平台级别与 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
实施的后果。
至于 "why",应该没什么大不了的,但在调试器中快速查看后,似乎 MSVC 实现与新线程握手以交换参数和资源。因此需要同步才能知道资源何时被移交。好像有道理。
std::thread
创建一个 C++ 线程。这意味着您可以依赖该线程中的 C++ 库。这意味着必须设置某些共享数据结构,这会强制同步(您可能会并行创建多个线程)。堆栈跟踪清楚地表明:std::_Cnd_waitX
显然是标准库的一部分,并且正在同步。同步在您提到的文档中被列入黑名单,因此这次崩溃并不令人意外。
在堆栈的更上方,我们看到 Concurrency::
。这特定于 Visual Studio 版本 up to VS2015。这意味着您可能会在 VS2015 中走运。在 DllMain
中执行线程同步不是 保证 崩溃。很有可能。
std::thread
的规范包含以下要求 (N4527 §30.3.1.2[thread.thread.constr]/6):
Synchronization: The completion of the invocation of the constructor synchronizes with the beginning of the invocation of the copy of f
.
(其中 f
是要在新创建的线程上执行的可调用实体。)
在新线程开始执行线程过程之前,std::thread
的构造函数无法 return。当创建一个新线程时,在调用线程过程之前,为DLL_THREAD_ATTACH
调用每个加载的DLL的入口点。为此,新线程必须获取加载程序锁。不幸的是,您现有的线程已经持有加载程序锁。
因此,你死锁了:现有线程在新线程开始执行线程过程之前无法释放加载程序锁,但新线程在获得加载程序锁之前无法执行线程过程,该锁由现有线程持有线程。
请注意 the documentation 明确建议不要从 DLL 入口点创建线程:
You should never perform the following tasks from within DllMain
: [...] Call CreateThread
. Creating a thread can work if you do not synchronize with other threads, but it is risky.
(该页面有一长串不应从 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
}
所以,这就是我所说的: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 中创建线程是完全错误的吗?这不是真的。从 微软文档"Best Practices for Creating DLLs": "如果您不与其他线程同步,则创建一个线程可以工作 threads”。所以 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 会 "synchronize with other threads"。
但是为什么?
我希望在 VS2015 中不再发生这种情况,我还没有测试它。
您将平台级别与 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
实施的后果。
至于 "why",应该没什么大不了的,但在调试器中快速查看后,似乎 MSVC 实现与新线程握手以交换参数和资源。因此需要同步才能知道资源何时被移交。好像有道理。
std::thread
创建一个 C++ 线程。这意味着您可以依赖该线程中的 C++ 库。这意味着必须设置某些共享数据结构,这会强制同步(您可能会并行创建多个线程)。堆栈跟踪清楚地表明:std::_Cnd_waitX
显然是标准库的一部分,并且正在同步。同步在您提到的文档中被列入黑名单,因此这次崩溃并不令人意外。
在堆栈的更上方,我们看到 Concurrency::
。这特定于 Visual Studio 版本 up to VS2015。这意味着您可能会在 VS2015 中走运。在 DllMain
中执行线程同步不是 保证 崩溃。很有可能。
std::thread
的规范包含以下要求 (N4527 §30.3.1.2[thread.thread.constr]/6):
Synchronization: The completion of the invocation of the constructor synchronizes with the beginning of the invocation of the copy of
f
.
(其中 f
是要在新创建的线程上执行的可调用实体。)
在新线程开始执行线程过程之前,std::thread
的构造函数无法 return。当创建一个新线程时,在调用线程过程之前,为DLL_THREAD_ATTACH
调用每个加载的DLL的入口点。为此,新线程必须获取加载程序锁。不幸的是,您现有的线程已经持有加载程序锁。
因此,你死锁了:现有线程在新线程开始执行线程过程之前无法释放加载程序锁,但新线程在获得加载程序锁之前无法执行线程过程,该锁由现有线程持有线程。
请注意 the documentation 明确建议不要从 DLL 入口点创建线程:
You should never perform the following tasks from within
DllMain
: [...] CallCreateThread
. Creating a thread can work if you do not synchronize with other threads, but it is risky.
(该页面有一长串不应从 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
}