Ser*_*tch 5 c++ dll winapi multithreading c++11
由于对DllMain的限制(我理解同样适用于DLL中的全局和静态对象构造函数和析构函数),因此具有异步文件写入/刷新线程的单例记录器这样简单的事情变得非常棘手.单例记录器在DLL中,我对可执行文件加载和卸载此DLL的时间影响有限.我可以强制该可执行文件在任何使用之前调用我的DLL初始化函数,因此在初始化函数中我可以使用临界区来保护变量,告知DLL是否已经初始化或者这次需要初始化.这从DllMain的方式初始化避免,这将导致死锁,因为我需要从初始化启动线程,线程调用DllMain
与DLL_THREAD_ATTACH
理由,并获得相同的加载程序锁,当我们正在初始化在已经获得一个DllMain
在DLL_PROCESS_ATTACH
事件.
thread
由于此错误,无法使用C++ 11 (MSVC++ 2013中未修复).所以我正在使用_beginthreadex()
,因为CreateThread
文档说:
调用C运行时库(CRT)的可执行文件中的线程应使用_beginthreadex和_endthreadex函数进行线程管理,而不是CreateThread和ExitThread; 这需要使用CRT的多线程版本.如果使用CreateThread创建的线程调用CRT,则CRT可以在低内存条件下终止进程.
但我无法控制可执行文件以确保在卸载DLL之前调用DLL中的某些取消初始化函数.因此,对于清理的唯一选择是DllMain
的DLL_PROCESS_DETACH
和全局/静态变量的析构函数.问题是,他们被称为与获得加载程序锁,所以我不能让那里的DLL线程正常退出,因为在一个正常的退出这些线程将尝试拨打DllMain
同DLL_THREAD_DETACH
,这将导致死锁(装载机锁再次).MSDN建议用来TerminateThread()
处理这个问题:
DLL A在其DllMain中获取DLL_PROCESS_DETACH消息,并为线程T设置一个事件,指示它退出.线程T完成其当前任务,使其自身处于一致状态,发出DLL A信号,并无限期等待.请注意,一致性检查例程应遵循与DllMain相同的限制,以避免死锁.DLL A终止T,知道它处于一致状态.
所以我害怕使用_beginthreadex()
+ TerminateThread()
pair而不是设计_endthreadex()
(如果线程正常返回,后者将由线程本身调用).
tl; dr考虑一个从其入口函数返回的线程与一个线程,该线程Sleep(INFINITE)
在其函数的末尾执行等待终止的事情(即在它获得资源一致并向终止线程发出信号表明它已准备好之后).thread_local
如果_endthreadex()
没有调用某些CRT或C++ 11资源(如)等泄漏或损坏等,而是TerminatThread()
被调用?
好.首先,我们将介绍几个小问题:
正如David在评论中提到的那样,您不需要使用_beginthreadex()而不是CreateThread().同样,在任何当前支持的Visual Studio和Windows版本上使用ExitThread()或类似代码而不是_endthreadex()也可以.
尽管MSDN文章说的是,但人们普遍认为使用TerminateThread()永远都不行.
人们普遍认为,只要您了解加载程序锁定所隐含的限制,就可以在DllMain的DLL_PROCESS_ATTACH处理中使用CreateThread().但是,如果您能够使用正确的初始化例程而不是DllMain,就像在您的情况下一样,那就更好了.
所以,如果我理解你的情况,可以归纳如下:
您的DLL需要一个或多个后台线程.
可执行文件在没有警告的情况下卸载DLL.
这有点愚蠢,但那不是你的错.幸运的是,处理并非不可能.
如果在可执行文件认为已卸载DLL之后线程可以继续运行,则可以使用FreeLibraryAndExitThread()模式.在初始化函数中,以及创建线程的任何其他位置,调用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线程,但如果您想严格遵守规则 - 如文档所述,这将是一种方法.
归档时间: |
|
查看次数: |
1951 次 |
最近记录: |