所以,这就是我所说的: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再也不会发生这种情况,我还没有测试过。
答案 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
}