std::thread вызывает тупик в DLLMain
Итак, вот о чем я говорю: стандартный - сложный.
В 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 совершенно неправильно? Это неправда. Из документа "Рекомендации по созданию DLL" от Microsoft: "Создание потока может работать, если вы не синхронизируете с другими потоками". Так что 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 будет "синхронизироваться с другими потоками".
Но почему?
Я надеюсь, что это никогда не случится снова в VS2015, я еще не проверял это.
4 ответа
Спецификация для std::thread
содержит следующее требование (N4527 §30.3.1.2[thread.thread.constr]/6):
Синхронизация: завершение вызова конструктора синхронизируется с началом вызова копии
f
,
(где f
является вызываемой сущностью, которая должна выполняться во вновь созданном потоке.)
Конструктор для std::thread
не может вернуться, пока новый поток не начнет выполнять процедуру потока. Когда создается новый поток, перед вызовом процедуры потока вызывается точка входа каждой загруженной DLL для DLL_THREAD_ATTACH
, Для этого новый поток должен получить блокировку загрузчика. К сожалению, ваш существующий поток уже содержит блокировку загрузчика.
Таким образом, вы зашли в тупик: существующий поток не может снять блокировку загрузчика, пока новый поток не начнет выполнять процедуру потока, но новый поток не сможет выполнить процедуру потока, пока он не сможет получить блокировку загрузчика, которая удерживается существующим потоком.
Обратите внимание, что документация настоятельно рекомендует не создавать потоки из точки входа DLL:
Вы никогда не должны выполнять следующие задачи изнутри
DllMain
: [...] ВызовCreateThread
, Создание потока может работать, если вы не синхронизируете его с другими потоками, но это рискованно.
(Эта страница содержит длинный список вещей, которые не следует делать с точки входа DLL; это только одна из них.)
Вы смешиваете уровень платформы с std
уровень. Вызов сырой функции winapi CreateThread
может работать в DllMain
, Но нет никаких гарантий о том, как std::thread
будет взаимодействовать с платформой. Хорошо известно, что делать подобные вещи крайне опасноDllMain
так что я не рекомендую это вообще. Если вы настаиваете на попытках, то вам нужно будет на цыпочках и напрямую вызвать вину, чтобы избежать последствий std
реализация.
Что касается "почему", это не должно иметь большого значения, но после очень быстрого просмотра в отладчике кажется, что реализация MSVC имеет рукопожатие с новым потоком для обмена аргументами и ресурсами. Поэтому требуется синхронизация, чтобы знать, когда ресурсы были переданы. Кажется разумным.
Использование 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
}
std::thread
создает поток C++. Это означает, что вы можете положиться на библиотеку C++ в этом потоке. Это означает, что необходимо настроить определенные общие структуры данных, что приведет к синхронизации (вы можете создать несколько потоков параллельно). Трассировка стека ясно показывает это: std::_Cnd_waitX
явно является частью стандартной библиотеки и четко синхронизируется. Синхронизация занесена в черный список в упомянутом вами документе, поэтому этот сбой не является большим сюрпризом.
Далее вверх по стеку мы видим Concurrency::
, Это относится к версиям Visual Studio до VS2015. Это означает, что вам может повезти в VS2015. Выполнение синхронизации потока в DllMain
не гарантированный сбой. Просто вполне возможно.