当从DllMain()锁定时,Visual Studio 2012死锁中的C++11 std::mutex

C++11 std::mutex in Visual Studio 2012 deadlock when locked from DllMain()

本文关键字:死锁 std mutex 2012 C++11 Studio DllMain 锁定 定时 Visual 当从      更新时间:2023-10-16

当互斥锁从DllMain()锁定时,我看到std::mutex出现死锁。下面是一个最小的DLL测试用例,它向我展示了这个问题。我的实际代码执行互斥锁,因为它使用的成员函数在正常函数期间也可以在初始化之外使用。

我认为问题在于main()线程的调用堆栈中的调度器与调度器派生的其他线程(可能)之间的死锁。死锁似乎发生在main()实际执行之前。

对于如何解决僵局,我将不胜感激。

简单DLL:

static void testFunc()
{
    std::mutex mtx;
    mtx.lock();
    mtx.unlock();
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        testFunc ();
        break;
    case DLL_THREAD_ATTACH:
        testFunc ();
        break;
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

在死锁点,进程中有两个线程:

Not Flagged >   6408    0   Main Thread Main Thread msvcr110d.dll!Concurrency::details::SchedulerBase::SchedulerBase    Normal
Not Flagged     7600    0   Worker Thread   ntdll.dll!_TppWaiterpThread@4() ntdll.dll!_NtDelayExecution@8   Normal

以下是main()线程的调用堆栈:

    ntdll.dll!_NtWaitForKeyedEvent@16() Unknown
    ntdll.dll!_TppWaitpSet@16() Unknown
    ntdll.dll!_TppSetWaitInterrupt@12() Unknown
    ntdll.dll!_RtlRegisterWait@24() Unknown
    kernel32.dll!_RegisterWaitForSingleObject@24()  Unknown
>   msvcr110d.dll!Concurrency::details::SchedulerBase::SchedulerBase(const Concurrency::SchedulerPolicy & policy) Line 152  C++
    msvcr110d.dll!Concurrency::details::ThreadScheduler::ThreadScheduler(const Concurrency::SchedulerPolicy & policy) Line 26   C++
    msvcr110d.dll!Concurrency::details::ThreadScheduler::Create(const Concurrency::SchedulerPolicy & policy) Line 34    C++
    msvcr110d.dll!Concurrency::details::SchedulerBase::CreateWithoutInitializing(const Concurrency::SchedulerPolicy & policy) Line 276  C++
    msvcr110d.dll!Concurrency::details::SchedulerBase::GetDefaultScheduler() Line 650   C++
    msvcr110d.dll!Concurrency::details::SchedulerBase::CreateContextFromDefaultScheduler() Line 567 C++
    msvcr110d.dll!Concurrency::details::SchedulerBase::CurrentContext() Line 399    C++
    msvcr110d.dll!Concurrency::details::LockQueueNode::LockQueueNode(unsigned int timeout) Line 616 C++
    msvcr110d.dll!Concurrency::critical_section::lock() Line 1017   C++
    msvcp110d.dll!mtx_do_lock(_Mtx_internal_imp_t * * mtx, const xtime * target) Line 65    C++
    msvcp110d.dll!_Mtx_lock(_Mtx_internal_imp_t * * mtx) Line 144   C++
    ConsoleApplicationDll.dll!std::_Mtx_lockX(_Mtx_internal_imp_t * * _Mtx) Line 68 C++
    ConsoleApplicationDll.dll!std::_Mutex_base::lock() Line 43  C++
    ConsoleApplicationDll.dll!testFunc() Line 16    C++
    ConsoleApplicationDll.dll!DllMain(HINSTANCE__ * hModule, unsigned long ul_reason_for_call, void * lpReserved) Line 29   C++
    ConsoleApplicationDll.dll!__DllMainCRTStartup(void * hDllHandle, unsigned long dwReason, void * lpreserved) Line 508    C
    ConsoleApplicationDll.dll!_DllMainCRTStartup(void * hDllHandle, unsigned long dwReason, void * lpreserved) Line 472 C
    ntdll.dll!_LdrpCallInitRoutine@16() Unknown
    ntdll.dll!_LdrpRunInitializeRoutines@4()    Unknown
    ntdll.dll!_LdrpInitializeProcess@8()    Unknown
    ntdll.dll!__LdrpInitialize@8()  Unknown
    ntdll.dll!_LdrInitializeThunk@8()   Unknown

第二个线程的调用堆栈很短:

>   ntdll.dll!_NtDelayExecution@8() Unknown
    ntdll.dll!__LdrpInitialize@8()  Unknown
    ntdll.dll!_LdrInitializeThunk@8()   Unknown

编辑1:

WinDbg确认是加载程序锁定问题:

PRIMARY_PROBLEM_CLASS:  APPLICATION_HANG_HungIn_LoaderLock

查看创建DLL的最佳实践文档:

您永远不应该在DllMain中执行以下任务:

  • 调用LoadLibrary或LoadLibraryEx(直接或间接)。这可能会导致死锁或崩溃
  • 与其他线程同步。这可能会导致死锁

使用QueueUserAPC()排队初始化似乎总是在main()之前执行,但在可怕的加载程序锁之外。这看起来像是解决了我的问题。

编辑1

经过一些测试,如果我从DllMain()对APC进行排队,APC方法似乎有效,但如果我从类的静态全局实例的ctor对APC进行队列,则它不起作用。IOW,对我来说,使用APC并不能在编译器和构建模式的所有可能组合中统一使用。