ale*_*ksv 0 c++ dll winapi multithreading
卸载DLL时,我偶然发现了Windows线程机制的意外行为.有一个工作线程对象的包,我试图在卸载DLL时(通过DllMain DLL_PROCESS_DETACH)优雅地完成它们.代码非常简单(我发送一个事件来完成线程的等待循环):
WaitForSingleObject( ThrHandle, INFINITE );
CloseHandle( ThrHandle );
Run Code Online (Sandbox Code Playgroud)
然而WaitForSingleObject挂起了整个事情.如果我在卸载DLL之前执行它,它工作正常.如何解决这种行为?
Har*_*ton 11
你不能等待线程在DllMain()中退出. 除非线程在收到DLL_PROCESS_DETACH时已经退出,否则这样做总是会死锁.这是预期的行为.
原因是通过加载程序锁定对DllMain()的调用进行了序列化.当调用ExitThread()时,它声明加载器锁定,以便它可以使用DLL_THREAD_DETACH调用DllMain().在该调用完成之前,该线程仍在运行.
所以DllMain正在等待线程退出,并且线程正在等待DllMain退出,这是一个典型的死锁情况.
另请参阅MSDN上的动态链接库最佳实践.
解决方案是向DLL添加一个新函数,以便在卸载DLL之前调用应用程序. 正如您所指出的,当您明确调用时,您的代码已经完美运行.
在向后兼容性要求使得无法添加此类函数的情况下,并且如果必须具有工作线程,请考虑将DLL拆分为两部分,其中一部分由另一部分动态加载.动态加载的部分将包含(至少)工作线程所需的所有代码.
当应用程序自身加载的DLL收到DLL_PROCESS_DETACH时,您只需将事件设置为通知线程退出然后立即返回.其中一个线程必须指定等待所有其他线程然后释放第二个DLL,您可以使用FreeLibraryAndExitThread()安全地执行.
(根据具体情况,特别是如果工作线程正在退出和/或作为常规操作的一部分创建新工作线程,您可能需要非常小心以避免竞争条件和/或死锁;如果您这样做可能会更简单使用线程池和回调而不是手动创建工作线程.)
在线程不需要使用除最简单的Windows API之外的任何特殊情况的情况下,可以使用线程池和工作回调来避免需要第二个DLL.退出回调后,您可以使用WaitForThreadpoolWorkCallbacks()进行检查,可以安全地卸载库 - 您无需等待线程本身退出.
这里的问题是回调必须避免任何可能采用加载程序锁定的Windows API.没有记录哪些API调用在这方面是安全的,并且它在不同版本的Windows之间有所不同.如果您正在调用比SetEvent或WriteFile更复杂的东西,或者如果您使用的是库而不是本机Windows API函数,则不得使用此方法.