线程对象的 WaitForSingleObject 在 DLL 卸载中不起作用
我偶然发现了卸载 DLL 时 Windows 线程机制的意外行为.有一组工作线程对象,我试图在卸载 DLL 时优雅地完成它们(通过 DllMain DLL_PROCESS_DETACH).代码非常简单(我确实发送了一个事件来完成线程的等待循环):
I've stumbled upon an unexpected behavior of Windows thread mechanism when DLL is unloaded. A have a pack of worker thread objects and I'm trying to finish them graciously when DLL is unloaded (via DllMain DLL_PROCESS_DETACH). The code is very simple (I do send an event to finish the thread's wait loop):
WaitForSingleObject( ThrHandle, INFINITE );
CloseHandle( ThrHandle );
然而,WaitForSingleObject 挂起整个事情.如果我在卸载 DLL 之前执行它,它工作正常.如何修复此行为?
Yet the WaitForSingleObject hangs the whole thing. It works fine if I perform it before DLL is unloaded. How this behavior can be fixed?
推荐答案
您不能在 DllMain() 中等待线程退出. 除非线程在收到 DLL_PROCESS_DETACH 时已经退出,否则这样做将总是死锁.这是预期的行为.
You can't wait for a thread to exit in DllMain(). Unless the thread had already exited by the time the DLL_PROCESS_DETACH was received, doing so will always deadlock. This is the expected behaviour.
这样做的原因是对 DllMain() 的调用是通过加载器锁序列化的.当 ExitThread() 被调用时,它声明加载器锁,以便它可以使用 DLL_THREAD_DETACH 调用 DllMain().在该调用完成之前,该线程仍在运行.
The reason for this is that calls to DllMain() are serialized, via the loader lock. When ExitThread() is called, it claims the loader lock so that it can call DllMain() with DLL_THREAD_DETACH. Until that call has finished, the thread is still running.
所以DllMain在等待线程退出,线程在等待DllMain退出,典型的死锁情况.
So DllMain is waiting for the thread to exit, and the thread is waiting for DllMain to exit, a classic deadlock situation.
另请参阅动态链接库最佳实践在 MSDN 上.
See also Dynamic-Link Library Best Practices on MSDN.
解决方案是向您的 DLL 添加一个新函数,供应用程序在卸载 DLL 之前调用.正如您所指出的,您的代码在显式调用时已经运行得非常好.
The solution is to add a new function to your DLL for the application to call before unloading the DLL. As you have noted, your code already works perfectly well when called explicitly.
如果向后兼容性要求无法添加这样的函数,并且如果您必须有工作线程,请考虑将您的 DLL 分成两部分,其中一部分由另一部分动态加载.动态加载的部分将(至少)包含工作线程所需的所有代码.
In the case where backwards compatibility requirements make adding such a function impossible, and if you must have the worker threads, consider splitting your DLL into two parts, one of which is dynamically loaded by the other. The dynamically loaded part would contain (at a minimum) all of the code needed by the worker threads.
当应用程序本身加载的 DLL 收到 DLL_PROCESS_DETACH 时,您只需设置事件以通知线程退出,然后立即返回.必须指定其中一个线程等待所有其他线程,然后释放第二个 DLL,您可以使用 FreeLibraryAndExitThread().
When the DLL that was loaded by the application itself receives DLL_PROCESS_DETACH, you just set the event to signal the threads to exit and then return immediately. One of the threads would have to be designated to wait for all the others and then free the second DLL, which you can do safely using FreeLibraryAndExitThread().
(根据情况,特别是如果工作线程正在退出和/或作为常规操作的一部分创建新线程,您可能需要非常小心以避免竞争条件和/或死锁;这可能是如果您使用线程池和回调而不是手动创建工作线程,则更简单.)
(Depending on the circumstances, and in particular if worker threads are exiting and/or new ones being created as part of regular operations, you may need to be very careful to avoid race conditions and/or deadlocks; this would likely be simpler if you used a thread pool and callbacks rather than creating worker threads manually.)
在特殊情况中,线程不需要使用除最简单的 Windows API 之外的任何其他 API,可能可以使用线程池和工作回调来避免需要第二个 DLL.回调退出后,您可以使用 WaitForThreadpoolWorkCallbacks(),卸载库是安全的 - 您不需要等待线程本身退出.
In the special case where the threads do not need to use any but the very simplest Windows APIs, it might be possible to use a thread pool and work callbacks to avoid the need for a second DLL. Once the callbacks have exited, which you can check using WaitForThreadpoolWorkCallbacks(), it is safe for the library to be unloaded - you do not need to wait for the threads themselves to exit.
这里的问题是回调必须避免任何可能占用加载程序锁定的 Windows API.没有记录在这方面哪些 API 调用是安全的,并且在不同版本的 Windows 之间有所不同.如果您要调用比 SetEvent 或 WriteFile 更复杂的东西,或者如果您使用的是库而不是本机 Windows API 函数,则不得使用这种方法.
The catch here is that the callbacks must avoid any Windows APIs that might take the loader lock. It is not documented which API calls are safe in this respect, and it varies between different versions of Windows. If you are calling anything more complicated than SetEvent or WriteFile, say, or if you are using a library rather than native Windows API functions, you must not use this approach.
相关文章