Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::thread cause deadlock in DLLMain

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.

like image 395
amanjiang Avatar asked Aug 27 '15 14:08

amanjiang


1 Answers

The specification for std::thread contains the following requirement (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.

(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 for DLL_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:

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.

(That page has a long list of things that should not be done from a DLL entry point; this is just one of them.)

like image 61
James McNellis Avatar answered Sep 19 '22 08:09

James McNellis