linux pthreads 上的 gcc 4.7 - 使用 __thread 的重要 thread_local 解决方法(无提升)

Posted

技术标签:

【中文标题】linux pthreads 上的 gcc 4.7 - 使用 __thread 的重要 thread_local 解决方法(无提升)【英文标题】:gcc 4.7 on linux pthreads - nontrivial thread_local workaround using __thread (no boost) 【发布时间】:2012-08-21 06:51:06 【问题描述】:

在 C++11 中,您可以拥有一个具有 thread_local 存储的重要对象:

class X  ... 

void f()

    thread_local X x = ...;
    ...

不幸的是,这个功能还没有在 gcc 中实现(从 4.7 开始)。

gcc 确实允许您拥有线程局部变量,但仅限于普通类型。

我正在寻找解决方法:

这是我目前所拥有的:

#include <iostream>
#include <type_traits>

using namespace std;

class X

public:
    X()  cout << "X::X()" << endl; ;
    ~X()  cout << "X::~X()" << endl; 
;

typedef aligned_storage<sizeof(X), alignment_of<X>::value>::type XStorage;

inline void placement_delete_x(X* p)  p->~X(); 

void f()

        static __thread bool x_allocated = false;
        static __thread XStorage x_storage;

        if (!x_allocated)
        
                new (&x_storage) X;
                x_allocated = true;

                // TODO: add thread cleanup that
                //     calls placement_delete_x(&x_storage)
        

        X& x = *((X*) &x_storage);


int main()

        f();

我需要帮助的是在当前线程退出时调用placement_delete_x(&x_storage)。我可以使用 pthreads 和/或 linux 中的机制来执行此操作吗?我需要向某种 pthread 清理堆栈添加函数指针和参数吗?

更新:

我认为pthread_cleanup_push 可能是我想要的:

http://www.kernel.org/doc/man-pages/online/pages/man3/pthread_cleanup_push.3.html

这会在正确的情况下调用清理处理程序吗?

更新 2:

看起来boost::thread_specific_ptr 最终使用destructor 参数调用pthread_key_create,而不是pthread_cleanup_push - 调用其tls 清理函数:

http://pubs.opengroup.org/onlinepubs/009696799/functions/pthread_key_create.html

目前还不清楚这两种方法之间的区别是什么,如果有的话。 ?

【问题讨论】:

也许Boost 可以提供帮助。 @MikeSeymour:见标题的最后两个词。 :) 在任何情况下, boost::thread_specifc_ptr 都需要动态内存分配 - 而在上述解决方案中,存储是 __thread 分配的。 @MikeSeymour:其实我撒了谎,你可以在上述解决方案中使用explicit thread_specific_ptr(void (*cleanup_function)(T*)); 来调用placement_delete_x。但是,唉,我不能使用boost。 boost::thread_specific_ptr 使用什么机制来调用清理处理程序? (我想我可以吃掉源头并找出来) 对不起,我没有发现你奇怪的禁令。我将在此处留下评论,因为它可能对那些能够使用有用的第三方库的人有所帮助。我想它会在 POSIX 平台上使用pthread_cleanup_push,但您必须查看源代码才能确定。 @MikeSeymour:不知道这是否很奇怪,根据 Google 编码指南,大多数 boost 库(包括 boost 线程)都是不允许的:例如,google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Boost。 【参考方案1】:

pthread_key_create 和朋友是您想要使用析构函数实现类型的线程特定变量的对象。不过这些一般都需要你管理创建和销毁变量的整个过程,我不确定你是否可以将它们与__thread结合使用。

pthread_cleanup_push 不适合。如果线程在使用该资源的(短)代码块期间退出,则它旨在允许释放资源;如您链接到的文档中所述,它必须与该函数同一级别的pthread_cleanup_pop 匹配,并且如果线程从其主函数返回,则不会调用处理程序。这意味着如果您希望线程局部变量在对函数的调用之间保持不变,则不能使用它。

为了那些不禁止第三方库的人的利益,Boost 提供了一种方便、可移植的方式来管理线程本地存储。

【讨论】:

抱歉,为什么不能将pthread_key_create__thread 结合使用? pthread_key_createdestructor 函数参数在线程退出时被调用 - 所以我认为我需要做的就是让它调用 placement_delete_x 对吗? 其实我不确定;我对__thread 的细节知之甚少,不知道您是否可以在它和pthreads 之间拼凑出某种混合体。我会调整答案。 我已经开始工作了,它非常简单。 __thread 只是将变量存储在每个线程的静态区域中,而不是在堆或全局静态区域中。我会尽快发布代码。 我添加了一个演示,看我的回答。【参考方案2】:

正如迈克所说,pthread_cleanup_push 不合适。正确的方法是使用pthread_key_create

我已经实现了一个小演示程序来展示如何做到这一点。我们实现了一个宏thread_local,您可以这样使用:

有了真正的 C++11 功能,它将是:

void f()

    thread_local X x(1,2,3);
    ...

有了这个:

void f()

    thread_local (X, x, 1, 2, 3);
    ...

this 和 boost::thread_specifc_ptr 的区别在于动态内存分配为零。一切都以__thread 持续时间存储。它的重量也明显更轻,但它是 gcc/linux 特定的。

概述:

    我们使用std::aligned_storage 为变量创建__thread 持续时间空间 在给定线程的第一个条目中,我们使用placement new 在存储中构造变量 我们还__thread为展示位置删除调用分配了一个链表条目 我们使用pthread_setspecific 来跟踪每个线程列表头 传递给pthread_key_create 的函数在线程退出时遍历调用放置删除的列表。

...

#include <iostream>
#include <thread>

using namespace std;

static pthread_key_t key;
static pthread_once_t once_control = PTHREAD_ONCE_INIT;

struct destructor_list

    void (*destructor)(void*);
    void* param;
    destructor_list* next;
;

static void execute_destructor_list(void* v)

    for (destructor_list* p = (destructor_list*) v; p != 0; p = p->next)
        p->destructor(p->param);


static void create_key()

    pthread_key_create(&key, execute_destructor_list);


void add_destructor(destructor_list* p)

    pthread_once(&once_control, create_key);

    p->next = (destructor_list*) pthread_getspecific(key);
    pthread_setspecific(key, p);


template<class T> static void placement_delete(void* t)  ((T*)t)->~T(); 

#define thread_local(T, t, ...)                         \
T& t = *((T*)                                           \
(                                                      \
    typedef typename aligned_storage<sizeof(T),         \
        alignment_of<T>::value>::type Storage;          \
    static __thread bool allocated = false;             \
    static __thread Storage storage;                    \
    static __thread destructor_list dlist;              \
                                                        \
    if (!allocated)                                     \
                                                       \
        new (&storage) T(__VA_ARGS__);                  \
        allocated = true;                               \
        dlist.destructor = placement_delete<T>;         \
        dlist.param = &storage;                         \
        add_destructor(&dlist);                         \
                                                       \
                                                        \
    &storage;                                           \
));

class X

public:
    int i;

    X(int i_in)  i = i_in; cout << "X::X()" << endl; ;

    void f()  cout << "X::f()" << endl; 

    ~X()  cout << "X::~X() i = " << i << endl; 
;

void g()

    thread_local(X, x, 1234);
    x.f();


int main()

    thread t(g);
    t.join();

注意事项:

    您需要为每个 pthread_* 调用添加错误检查。我只是为了展示而将其删除。 它使用__thread,这是一个 GNU 扩展 它使用 表达式语句 将辅助 __thread 变量名称保留在父范围之外。这也是一个 GNU 扩展。

【讨论】:

我对 C++ 还是很陌生。你能解释一下为什么在这种情况下需要对齐存储吗?顺便说一句,在制定解决方案方面做得很好。这正是我想要的。 @chamibuddhika: (1) 对齐存储为类型 T 的对象提供适当对齐的未初始化存储。否则存储将未对齐。 (2) 这个问题和解决方案现在已经被 C++11 和后续的真正的thread_local 核心语言特性所淘汰。见:en.cppreference.com/w/cpp/language/storage_duration 您是否在任何地方(例如 github)发布了完整的解决方案?

以上是关于linux pthreads 上的 gcc 4.7 - 使用 __thread 的重要 thread_local 解决方法(无提升)的主要内容,如果未能解决你的问题,请参考以下文章

gcc编译出现 undefined reference to ‘pthread_create‘ 的解决方法

MacOS 上的 gcc:奇怪的线程错误

AryaLinux 2016.08 发布,Linux 内核更新至 4.7

无法将共享库与 -mx32 和 gcc 4.7 或 gcc 4.8 链接

gcc 和 pthreads 的未定义引用错误 _dl_stack_flags

gcc/clang编译带pthread.h头文件的源码时需要的参数