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