C++ 如何在Windows上的线程出口处释放TLS插槽中的对象?
例如,在多线程程序中:C++ 如何在Windows上的线程出口处释放TLS插槽中的对象?,c++,windows,thread-local-storage,C++,Windows,Thread Local Storage,例如,在多线程程序中: struct TLSObject; void foo() { TLSObject* p = TlsGetValue(slot); if (p == 0) { p = new TLSObject; TlsSetValue(slot, p); } // doing something with p } 第一次在任何线程中调用foo()将生成一个新的TLSObject 我的问题是: 如何删除TLSObject(
struct TLSObject;
void foo()
{
TLSObject* p = TlsGetValue(slot);
if (p == 0) {
p = new TLSObject;
TlsSetValue(slot, p);
}
// doing something with p
}
第一次在任何线程中调用foo()将生成一个新的TLSObject
我的问题是:
如何删除TLSObject(如果我不使用boost::thread和boost::thread_specific_ptr)
特定于线程的ptr可以在线程退出时执行清理工作,
但这取决于boost::thread,我想不是普通的OS线程,
而且速度很慢。例如,您应该能够使用多种作用域退出机制中的一种来实现这一点
另一种选择是将TLSObject包装到一个RAII类中,该类在RAII包装被销毁时释放该对象。这是一种非常常见的资源管理模式,在这里绝对适用。boost::thread\u specific\u ptr'应该适用于任何线程(根据我问题的答案:)
说它慢,是的,它不理想。但是,您可以使用任何您想要的普通TLS机制(我使用了特定于GCC的修饰符),然后创建一个额外的特定于线程的ptr来清理数据(创建一个真正TLS指针的包装器)。因此,创建和删除TLS的成本有点高,但访问不会受到影响。使用(和相关的
Fls*
函数)代替TlsAlloc
。使用FLS,您可以注册一个清理回调,在线程终止之前,操作系统将调用该回调,从而为您提供清理的机会。好的。对于Windows Vista及以上版本,正如James McNellis所说,我们
我可以用
对于DLL,如果reason参数等于
要分离DLL_线程,我们需要进行清理。另一种选择可能是
使用Prawdellmain,它就像另一个DllMain一样,你可以
从中找到它
对于EXE,我们可以使用TLS回调,请查看
当然,还有。在里面
实际上,它可以在WindowsXP上运行,但我发现优化
可能会使它无效,所以要小心优化,或者
对回调函数指针的显式引用
将下面的代码保存到tls.cpp并将其添加到项目中,否
不管是exe还是dll,它都能工作。请注意,对于
可以调用Windows Vista及更高版本的onThreadExit函数
两次-一次来自dll\U回调,一次来自tls\U回调
#include <windows.h>
extern void onThreadExit();
static void NTAPI tls_callback(PVOID, DWORD reason, PVOID)
{
if (reason == DLL_THREAD_DETACH) {
onThreadExit();
}
}
static BOOL WINAPI dll_callback(LPVOID, DWORD reason, LPVOID)
{
if (reason == DLL_THREAD_DETACH) {
onThreadExit();
}
return TRUE;
}
#pragma section(".CRT$XLY",long,read)
extern "C" __declspec(allocate(".CRT$XLY")) PIMAGE_TLS_CALLBACK _xl_y = tls_callback;
extern "C"
{
extern BOOL (WINAPI * const _pRawDllMain)(HANDLE, DWORD, LPVOID) = &dll_callback;
}
#pragma comment(linker, "/INCLUDE:__tls_used")
#pragma comment(linker, "/INCLUDE:__xl_y")
#包括
extern void onThreadExit();
静态无效NTAPI tls_回调(PVOID、DWORD reason、PVOID)
{
如果(原因==DLL\u线程\u分离){
onThreadExit();
}
}
静态BOOL-WINAPI-dll_回调(LPVOID、DWORD-reason、LPVOID)
{
如果(原因==DLL\u线程\u分离){
onThreadExit();
}
返回TRUE;
}
#pragma部分(“.CRT$XLY”,长,读)
外部“C”declspec(allocate(“.CRT$XLY”))PIMAGE\u TLS\u CALLBACK\u xl\u y=TLS\u CALLBACK;
外部“C”
{
外部BOOL(WINAPI*const\u prawdlmain)(HANDLE、DWORD、LPVOID)=&dll\u回调;
}
#pragma注释(链接器,“/INCLUDE:\uu tls\u used”)
#pragma注释(链接器,“/INCLUDE:\uuuuuxl\uy”)
如果您认为它不清楚,请使用boost的
复杂性是隐藏的。事实上,上面的代码是简化的
boost tls的版本。如果您不想使用boost,请打开
Windows系统这是另一种选择
或者,一种更通用的方法:。范围退出不适用于线程生存期。他希望在线程结束时清理对象,而不是在作用域退出时。我刚才做了一个测试,线程特定的ptr可以与_beginthreadex一起工作,这是个好消息。现在,我使用Win32 TLS进行访问,使用特定于线程的ptr进行清理,它可以正常工作!这个想法太棒了,非常感谢。詹姆斯,我知道你是VC工程师,标准库和VC编译器使用Fls还是Tls?使用Fls代替Tls是否总是安全的(是否确实推荐)?我知道与Tls相比,Fls的插槽非常有限。最新版本的运行库在所有可用的操作系统上都使用Fls(Fls是在Windows Vista中引入的)。有128个FLS插槽可供进程使用(FLS_最大可用)。正如您所说,FLS是在Windows Vista中引入的,那么Windows XP呢?我能有其他选择吗?@amanjiang不,Windows XP已经死了。我不知道有哪种解决方案可以在Windows XP上运行。非常有创意,但需要注意的是:这种方法很脆弱,将来可能会悄无声息地崩溃(因为不会调用tls_回调),它也不能保证回调后不会调用其他CRT代码,这取决于您正在做什么,可能会再次分配TLS。