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_create
的 destructor
函数参数在线程退出时被调用 - 所以我认为我需要做的就是让它调用 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‘ 的解决方法
AryaLinux 2016.08 发布,Linux 内核更新至 4.7
无法将共享库与 -mx32 和 gcc 4.7 或 gcc 4.8 链接