gpt4 book ai didi

c++ - 清理DLL : _endthreadex() vs TerminateThread()中的线程

转载 作者:塔克拉玛干 更新时间:2023-11-03 01:34:52 28 4
gpt4 key购买 nike

由于restrictions on DllMain(我知道这对DLL中的全局和静态对象构造函数和析构函数同样适用),像带异步文件写入/刷新线程的单例记录器这样的简单事情变得太棘手了。单例记录器位于DLL中,并且对可执行文件的加载和卸载时的影响有限。我可以强制该可执行文件在使用前调用它的DLL初始化函数,因此在初始化函数中,我可以使用关键部分来保护一个变量,该变量告诉DLL是否已经初始化或这次是否需要初始化。通过这种方式避免了DllMain的初始化,这将导致死锁,因为我需要从初始化启动线程,并且线程使用DllMain的原因调用DLL_THREAD_ATTACH,并且获得与我们在DllMain上进行初始化时已经获得的加载程序锁相同的加载程序锁。 DLL_PROCESS_ATTACH事件。

由于this bug(MSVC++ 2013中未修复),因此无法使用C++ 11 thread。所以我正在使用_beginthreadex(),因为 CreateThread documentation说:

A thread in an executable that calls the C run-time library (CRT) should use the _beginthreadex and _endthreadex functions for thread management rather than CreateThread and ExitThread; this requires the use of the multithreaded version of the CRT. If a thread created using CreateThread calls the CRT, the CRT may terminate the process in low-memory conditions.



但是我无法控制可执行文件,以确保在卸载DLL之前调用DLL的一些反初始化功能。因此,清理的唯一选项是 DllMainDLL_PROCESS_DETACH和全局/静态变量的析构函数。问题在于它们是在获得加载程序锁的情况下调用的,所以我无法使DLL线程在那里正常退出,因为正常退出时那些线程会尝试使用 DllMain调用 DLL_THREAD_DETACH,这将导致死锁(再次加载程序锁)。 MSDN建议使用 TerminateThread()处理此问题:

DLL A gets a DLL_PROCESS_DETACH message in its DllMain and sets an event for thread T, signaling it to exit. Thread T finishes its current task, brings itself to a consistent state, signals DLL A, and waits infinitely. Note that the consistency-checking routines should follow the same restrictions as DllMain to avoid deadlocking. DLL A terminates T, knowing that it is in a consistent state.



因此,我担心使用 _beginthreadex() + TerminateThread()对,而不是设计的 _endthreadex()(如果线程正常返回,则线程本身将调用后者)。

tl; dr 考虑一个从其入口函数返回的线程与一个在其函数末尾执行 Sleep(INFINITE)之类的线程等待终止的线程(即,在获得资源一致并向终止线程发出信号后,该线程已终止)准备好)。如果未调用 thread_local而是调用 _endthreadex(),是否会导致某些CRT或C++ 11资源(例如 TerminatThread())等泄漏或损坏等?

最佳答案

好的。首先,让我们介绍一些要点:

  • 正如David在评论中提到的you don't need to use _beginthreadex() rather than CreateThread()。同样,在任何当前受支持的Visual Studio和Windows版本上,也可以使用ExitThread()或类似名称代替_endthreadex()。
  • 尽管MSDN文章说了什么,the accepted wisdom is that it is never OK to use TerminateThread()
  • 如果您了解加载程序锁所隐含的限制,则通常也可以接受,可以在DllMain的DLL_PROCESS_ATTACH处理中使用CreateThread()。但是,如果能够使用适当的初始化例程而不是DllMain(如您的情况),那就更好了。

  • 因此,如果我正确理解了您的情况,可以总结如下:
  • 您的DLL需要一个或多个后台线程。
  • 可执行文件无需警告就卸载DLL。

  • 这有点愚蠢,但这不是你的错。幸运的是,并非没有可能。

    如果可接受的线程在可执行文件认为已卸载DLL后继续运行,则可以 use the FreeLibraryAndExitThread() pattern。在初始化函数以及创建线程的其他任何地方,调用 GetModuleHandleEx()来增加DLL引用计数。这样,当可执行文件调用 FreeLibrary()时,如果任何线程仍在运行,则该DLL实际上不会被卸载。线程通过调用 FreeLibraryAndExitThread()退出,并保持引用计数。

    但是,这种方法可能无法直接满足您的需求,因为它不允许您检测可执行文件何时卸载了库,因此您可以发出信号以终止线程。

    可能会有更聪明的解决方案,但是我建议您使用辅助DLL。这个想法是,帮助程序DLL(而不是主DLL)会跟踪线程引用计数,即,每次创建后台线程时都加载帮助程序DLL,并在每次后台线程退出时将其卸载。辅助DLL只需要包含一个函数,该函数调用SetEvent(),然后调用FreeLibraryAndExitThread()。

    当通知后台线程DLL正在被卸载时,它会清理,然后调用帮助程序DLL来设置事件并退出线程。设置事件后,您的主DLL的分离例程将知道该线程不再运行来自主DLL的代码。一旦每个后台线程完成清理,就可以安全地卸载主DLL-线程仍在运行并不重要,因为它们正在运行的是来自帮助DLL而不是主DLL的代码。一旦最后一个线程调用FreeLibraryAndExitThread(),则辅助DLL将自动卸载。

    大约一年后再来看一次,可能更安全的做法是:使主DLL除了初始化函数和程序正在调用的其他函数外,不包含任何东西,外加一个DllMain,用于向后台线程发送信号。退出,并具有包含其他所有内容的辅助DLL。

    特别是,如果辅助DLL包含您的后台线程需要的所有代码,则在后台线程仍在运行时卸载主DLL是安全的。

    这种变体的优点是,当您的后台线程看到退出信号时,主DLL是否已经卸载都没有关系,因此您的DllMain函数不必在保持加载程序锁定的同时等待。这样,如果后台线程之一无意中执行了需要加载程序锁定的操作,则该过程不会死锁。

    作为同一个想法的变体,如果您确实真的不想在CRT线程上使用FreeLibraryAndExitThread(),则可以在辅助DLL中放置一个额外的线程来协调卸载。该线程将从CreateThread()开始,并且不使用任何CRT函数,因此通过FreeLibraryAndExitThread()退出无疑是安全的。它的唯一职责是在卸载辅助DLL之前等待所有其他线程退出。

    确实不再需要再区分CRT和非CRT线程,但是,如果您要严格遵循所记录的规则,这将是实现此目的的一种方法。

    关于c++ - 清理DLL : _endthreadex() vs TerminateThread()中的线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39241400/

    28 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com