破坏Thread本地存储中的静态类成员

时间:2011-01-04 09:12:56

标签: c++ multithreading synchronization

我正在编写一个快速的多线程程序,我想避免同步(需要同步的函数必须被称为每秒5,000,000次,所以即使是互斥量也会太重)。 / p>

场景是:我有一个类的全局实例,每个线程都可以访问它。为了避免同步,除了一堆类成员之外,类中的所有数据都是只读的,然后在TLS中声明(使用__thread或__declspec(thread))。

不幸的是,为了使用编译器提供的__thread接口,类成员必须是静态的,没有构造函数/解构函数。我使用的类当然有自定义构造函数,所以我作为类成员声明指向这些类的指针(类似于静态__thread MyClass * _object)。

然后,第一次线程从全局实例调用一个方法时,我会做类似“(如果_object == NULL)object = new MyClass(...)”。

我最大的问题是:是否有一种智能的方式来释放这个分配的内存?这个全局类来自一个库,并且它被程序中的许多线程使用,并且每个线程以不同的方式创建(即每个线程执行不同的函数)并且每次都不能放入代码的snipplet线程将要终止。 谢谢你们。

5 个答案:

答案 0 :(得分:2)

TLS清理通常在传递DLL_THREAD_DETACH时在DllMain中完成。

如果您的代码全部在EXE而不是DLL中,那么您可以创建一个EXE加载的虚拟DLL,然后在DLL_THREAD_DETACH上调用EXE。 (我不知道在线程终止时运行EXE代码的更好方法。)

DLL有两种方法可以回调到EXE:一种是EXE可以像DLL一样导出函数,DLL代码可以在EXE的模块句柄上使用GetProcAddress。一个更简单的方法是给DLL一个init函数,EXE调用它来显式传递一个函数指针。

请注意,您在DllMain中可以执行的操作是有限的(不幸的是,限制没有正确记录),因此您应该尽量减少以这种方式完成的任何工作。不要运行任何复杂的析构函数;只需使用像HeapAlloc这样的直接kernel32.dll API释放内存并释放TLS插槽。

另请注意,加载DLL时已经运行的线程不会获得DLL_THREAD_ATTACH(但如果在加载DLL时它们仍然会出现DLL_THREAD_DETACH),并且您将获得(仅)最后一个线程退出时的DLL_PROCESS_DETACH。

答案 1 :(得分:2)

如果您只想要一个通用的清理功能,您仍然可以使用boost thread_specific_ptr。您不需要实际使用存储在那里的数据,但您可以利用自定义清理功能。只要让这个功能变得随心所欲,你就可以随心所欲。查看pthread函数pthread_key_create以获取直接pthreads函数调用。


遗憾的是,没有简单的答案,至少我没有遇到过。也就是说,在线程退出时没有删除复杂对象的常用方法。但是,没有什么能阻止你自己做这件事。

您需要在线程退出时注册自己的处理程序。使用pthreads pthread_cleanup_push。我不知道窗户上是什么。这当然不是跨平台的。但是,大概你可以完全控制线程的启动及其入口点。您可以在从线程返回之前简单地显式调用清理函数。 我知道你提到你不能添加这个片段,在这种情况下你将被调用OS特定的函数来添加一个清理例程。

显然,为所有分配的对象创建清理函数可能很烦人。因此,您应该再创建一个线程局部变量:对象的析构函数列表。对于您创建的每个特定于线程的变量,您将把析构函数推送到此列表中。如果您没有共同的线程入口点,则必须按需创建此列表:调用一个全局函数来获取析构函数并根据需要创建列表,然后添加析构函数。

这个析构函数的确切含义在很大程度上取决于您的对象层次结构(您可能有简单的boost绑定语句,shared_ptr,基类中的虚拟析构函数或其组合)。

然后,您的通用清理功能可以遍历此列表并执行所有析构函数。

答案 2 :(得分:2)

在C ++ 11 中很容易实现:

static thread_local struct TlsCleaner {
    ~TlsCleaner() {
        cleanup_tls();
    }
} tls_cleaner;

cleanup_tls()将在每个线程终止时执行(假设线程是使用C ++ API创建的,如std::thread)。

但是,你可以直接在他们的析构函数中清理TLS对象(也会迅速执行)。例如:static thread_local std::unique_ptr<MyClass> pMyClass;将在线程终止时删除MyClass

在C ++ 11之前,您可以使用类似GNU&#34;链接器集的#ha;或MSVC&#34; _tls_used&#34;回调。

或者,从Windows 6(Vista)开始, FlsAlloc ,它接受清理回调。

答案 3 :(得分:1)

如果您正在使用pthreads,您可以查看清理操作吗?

http://man.yolinux.com/cgi-bin/man2html?cgi_command=pthread_cleanup_push

您可以在创建线程本地对象之后立即执行清理操作,以便在退出时该对象被销毁。不确定winapi等价物是什么......

答案 4 :(得分:1)