So, this is what I'm talking about: std is complex.
In VS2013 this simple program will cause a deadlock.
#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;
}
Create a thread in DLLMain is totally wrong ? It's not true. From the document "Best Practices for Creating DLLs" of Microsoft: "Creating a thread can work if you do not synchronize with other threads". So CreateThread works, _beginthreadex works, and boost::thread works, but std::thread will not work. This is the call stack:
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
Okay, std::thread will "synchronize with other threads".
But why ?
I hope this never happens again in VS2015, I didn't test it yet.
You're mixing the platform level with
std
level. Calling the raw winapi functionCreateThread
can work inDllMain
. But there's no guarantee about howstd::thread
will interact with the platform. It's well known that it's extremely dangerous to be doing things like this inDllMain
, so I don't recommend it at all. If you insist on trying, then you're going to need to tip-toe around and call the winapi directly, avoiding consequences of thestd
implementation.As for "why", it shouldn't really matter, but after a very quick look in the debugger, it seems that the MSVC implementation has a handshake with the new thread for exchanging arguments and resources. So it requires synchronization to know when the resources have been handed off. Seems reasonable.
std::thread
creates a C++ thread. It means you can rely on the C++ library in that thread. This means certain shared data structures must be set up, which force synchronization (you might create multiple threads in parallel). The stack trace clearly shows this:std::_Cnd_waitX
is clearly a part of the Standard Library, and is clearly synchronizing. Synchronizing is blacklisted in the document you mentioned, so this crash isn't a big surprise.Further up the stack we see
Concurrency::
. This is specific to Visual Studio versions up to VS2015. This means you may luck out in VS2015. Doing thread synchronization inDllMain
is not a guaranteed crash. Just quite possible.The specification for
std::thread
contains the following requirement (N4527 §30.3.1.2[thread.thread.constr]/6):(where
f
is the callable entity which is to be executed on the newly created thread.)The constructor for the
std::thread
cannot return until the new thread starts executing the thread procedure. When a new thread is created, before the thread procedure is invoked, the entry point of each loaded DLL is invoked forDLL_THREAD_ATTACH
. To do this, the new thread must acquire the loader lock. Unfortunately, your existing thread already holds the loader lock.Thus, you deadlock: the existing thread cannot release the loader lock until the new thread starts executing the thread procedure but the new thread cannot execute the thread procedure until it can acquire the loader lock, which is held by the existing thread.
Note that the documentation expressly recommends against creation of threads from the DLL entry point:
(That page has a long list of things that should not be done from a DLL entry point; this is just one of them.)
Use
detach()
member function to fix the crash. Example: