c++ - thread - 線程對象的WaitForSingleObject在DLL卸載中不起作用



thread join c++ 11 (1)

我已經偶然發現DLL卸載時Windows線程機制的意外行為。 有一個工作線程對象的包,我試圖完成他們恩惠當DLL卸載(通過DllMain DLL_PROCESS_DETACH)。 代碼非常簡單(我發送一個事件來完成線程的等待循環):

WaitForSingleObject( ThrHandle, INFINITE );
CloseHandle( ThrHandle );

然而,WaitForSingleObject掛起整個事情。 它工作正常,如果我在卸載DLL之前執行它。 這種行為如何解決?


你不能等待一個線程退出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函數,則不得使用此方法。