C++11 std::mutex в тупике Visual Studio 2012 при блокировке из DllMain()
Я вижу тупик с std::mutex
когда мьютекс заблокирован от DllMain()
Ниже приведен минимальный тестовый пример 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
2 ответа
Кажется, что с помощью QueueUserAPC()
Инициализация в очереди всегда выполняется перед main(), но из-за ужасной блокировки загрузчика. Это похоже на решение моей проблемы.
РЕДАКТИРОВАТЬ 1
После некоторого тестирования кажется, что метод APC работает, если я ставлю APC в очередь DllMain()
но это не работает, если я ставлю APC в очередь из ctor статического глобального экземпляра класса. Таким образом, использование APC не является единообразным для всех возможных комбинаций компиляторов и режимов сборки.
Ознакомьтесь с документом "Рекомендации по созданию библиотек DLL":
Вы никогда не должны выполнять следующие задачи из DllMain:
- Вызовите LoadLibrary или LoadLibraryEx (прямо или косвенно). Это может вызвать тупик или сбой.
- Синхронизировать с другими потоками. Это может вызвать тупик.