销毁线程本地存储中的静态类成员
我正在编写一个快速的多线程程序,并且我想避免同步(需要同步的函数必须每秒调用 5,000,000 次,所以即使是互斥体也会太重)。
场景是:我有一个类的单个全局实例,每个线程都可以访问它。为了避免同步,类内的所有数据都以只读方式访问,除了一堆类成员之外,然后在 TLS 中声明它们(使用 __thread 或 __declspec(thread))。
不幸的是,为了使用编译器提供的 __thread 接口,类成员必须是静态的并且没有构造函数/解构函数。我使用的类当然有自定义构造函数,因此我将指向该类的指针声明为类成员(类似于 static __thread MyClass* _object)。
然后,当线程第一次从全局实例调用方法时,我将执行类似“(if _object == NULL) object = new MyClass(...)”的操作。
我最大的问题是:有没有一种聪明的方法来释放分配的内存?这个全局类来自一个库,它被程序中的许多线程使用,并且每个线程都以不同的方式创建(即每个线程执行不同的函数),并且我不能每次都添加代码片段线程将终止。 谢谢你们。
I'm writing a fast multi-thread program, and I want to avoid syncronization (the function which would need to be syncronized must be called something like 5,000,000 times per second, so even a mutex would be too heavy).
The scenario is: I have a single global instance of a class, and each thread can access it. In order to avoid syncronization, all the data inside the class is accessed read-only, except for a bunch of class members, which are then declared in TLS (with __thread or __declspec(thread)).
Unfortunately, in order to use the __thread interface offered by the compiler, the class members have to be static and without constructors/deconstructors. The classes I use of course have custom constructors, so I'm declaring, as class members, a pointer to that classes (something like static __thread MyClass* _object).
Then, the first time a thread calls a method from the global instance, I'll do something like "(if _object == NULL) object = new MyClass(...)".
My biggest problem is: is there a smart way to free this allocated memory? This global class is from a library, and it is used by many threads in the program, and each thread is created in a different way (i.e. each thread executes a different function) and I can't put a snipplet of code each time the thread is going to terminate.
Thank you guys.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
在 C++11 中,这很容易实现:
cleanup_tls()
将在每次线程终止时执行(前提是线程是使用std::thread 等 C++ API 创建的) )。
但是,您也可以直接在其析构函数中清理 TLS 对象(这也将立即执行)。例如: static thread_local std::unique_ptrpMyClass; 将在线程终止时删除
MyClass
。在 C++11 之前,您可以使用 GNU“链接器集”或 MSVC“
或者,从 Windows 6 (Vista) 开始,FlsAlloc,接受清理回调。
In C++11 this is easily achieved:
cleanup_tls()
will execute on every thread termination (provided the thread is created using C++ API likestd::thread
).But then, you could just as well cleanup TLS objects directly in their destructors (which will also promptly execute). For example:
static thread_local std::unique_ptr<MyClass> pMyClass;
will deleteMyClass
when a thread terminates.Before C++11 you can use hacks like the GNU "linker sets" or MSVC "_tls_used" callback.
Or, starting from Windows 6 (Vista), FlsAlloc, which accepts a cleanup callback.
TLS 清理通常在 DllMain 中传递 DLL_THREAD_DETACH 时完成。
如果您的代码全部位于 EXE 而不是 DLL 中,那么您可以创建一个虚拟 DLL,由 EXE 加载,然后在 DLL_THREAD_DETACH 上回调到 EXE。 (我不知道有更好的方法让 EXE 代码在线程终止时运行。)
DLL 有几种回调 EXE 的方法:一种是 EXE 可以像 DLL 一样导出函数,而 DLL代码可以在 EXE 的模块句柄上使用 GetProcAddress。一种更简单的方法是为 DLL 提供一个 init 函数,EXE 调用该函数来显式传递函数指针。
请注意,您在 DllMain 中可以执行的操作是有限的(不幸的是,这些限制没有正确记录),因此您应该尽量减少以这种方式完成的任何工作。不要运行任何复杂的析构函数;只需使用直接的 kernel32.dll API(如 HeapAlloc)释放内存并释放 TLS 插槽。
另请注意,对于加载 DLL 时已经运行的线程,您不会获得 DLL_THREAD_ATTACH(但如果它们在加载 DLL 时退出,您仍然会获得 DLL_THREAD_DETACH),并且您将(仅)获得 DLL_PROCESS_DETACH最终线程退出。
TLS clean-up is usually done in DllMain when it is passed DLL_THREAD_DETACH.
If your code is all in an EXE and not a DLL then you could create a dummy DLL that the EXE loads which in turn calls back into the EXE on DLL_THREAD_DETACH. (I don't know of a better way to have EXE code run on thread termination.)
There are a couple of ways for the DLL to call back into the EXE: One is that EXEs can export functions just like DLLs, and the DLL code can use GetProcAddress on the EXE's module handle. An easier method is to give the DLL an init function which the EXE calls to explicitly pass a function pointer.
Note that what you can do within DllMain is limited (and unfortunately the limits are not properly documented), so you should minimize any work done this way. Don't run any complex destructors; just free memory using a direct kernel32.dll API like HeapAlloc and free the TLS slot.
Also note that you won't get a DLL_THREAD_ATTACH for threads that were already running when your DLL was loaded (but you will still get DLL_THREAD_DETACH if they exit while the DLL is loaded), and that you'll get (only) a DLL_PROCESS_DETACH when the final thread exits.
如果您只想要一个通用的清理函数,您仍然可以使用 boost thread_specic_ptr。您不需要实际使用存储在那里的数据,但您可以利用自定义清理功能。只要让这个函数变得任意,你就可以做任何你想做的事情。查看 pthread 函数
pthread_key_create
以了解直接 pthread 函数调用。不幸的是,没有简单的答案,至少我还没有遇到过。也就是说,没有通用的方法可以在线程退出时删除复杂对象。然而,没有什么可以阻止你自己做这件事。
您需要在线程退出时注册自己的处理程序。对于 pthreads,这将是
pthread_cleanup_push
。我不知道windows上的是什么。这当然不是跨平台的。但是,想必您可以完全控制线程的启动及其入口点。您可以在从线程返回之前简单地显式调用清理函数。 我知道您提到过您无法添加此代码片段,在这种情况下,您将不得不调用操作系统特定的函数来添加清理例程。显然,为所有分配的对象创建清理函数可能会很烦人。因此,您应该再创建一个线程局部变量:对象的析构函数列表。对于您创建的每个特定于线程的变量,您将向此列表推送一个析构函数。如果您没有公共线程入口点,则必须根据需要创建此列表:有一个要调用的全局函数,该函数接受您的析构函数并根据需要创建列表,然后添加析构函数。
这个析构函数的具体外观在很大程度上取决于您的对象层次结构(您可能有简单的 boost 绑定语句、shared_ptr、基类中的虚拟析构函数或它们的组合)。
然后,您的通用清理函数可以遍历此列表并执行所有析构函数。
If you just want a generic cleanup function you can still use boost thread_specific_ptr. You don't need to actually use the data stored there, but you can take advantage of the custom cleanup function. Just make that function something arbitrary and you can do whatever you want. Look at the pthread function
pthread_key_create
for a direct pthreads function call.There is unfortunately no easy answer, at least not that I've come across yet. That is, there is no common way to have complex objects deleted at thread exit time. However, there's nothing stopping you from doing this on your own.
You will need to register your own handlers at thread exit time. With pthreads that would be
pthread_cleanup_push
. I don't know what it is on windows. This is of course not cross-platform. But, presumably you have full control of the starting of the thread and its entry-point. You could simply explicitly call a cleanup function just before returning from your thread. I know you mentioned you can't add this snippet, in which case you'll be left calling the OS specific function to add a cleanup routine.Obviously creating cleanup functions for all objects allocated could be annoying. So instead you should create one more thread local variable: a list of destructors for objects. For each thread-specific variable you create you'll push a destructor onto this list. This list will have to be created on demand if you don't have a common thread entry point: have a global function to call which takes your destructor and creates the list as necessary, then adds the destructor.
Exactly what this destructor looks like depends heavily on your object hierarchy (you may have simple boost bind statements, shared_ptr's, a virtual destructor in a base class, or a combination thereof).
Your generic cleanup function can then walk through this list and perform all the destructors.
如果您使用 pthreads,您可以查看清理操作吗?
http://man.yolinux.com/cgi-bin/man2html?cgi_command=pthread_cleanup_push
您可以在创建线程本地对象后立即推送清理操作,以便在退出时该对象被销毁。不确定 winapi 的等价物是什么......
If you are using pthreads you could look at the cleanup operations?
http://man.yolinux.com/cgi-bin/man2html?cgi_command=pthread_cleanup_push
You can push a cleanup operation just after the creation of the thread local object, such that on exit that object gets destroyed. Not sure what the winapi equivalent is...
Boost 线程本地存储
Boost Thread Local Storage