如何分配线程局部存储?

57

我在函数中有一个静态变量,但我希望它是基于每个线程静态的。

如何分配内存来创建我的C++类实例,以便每个线程都拥有自己的类实例的副本?

AnotherClass::threadSpecificAction()
{
  // How to allocate this with thread local storage?
  static MyClass *instance = new MyClass();

  instance->doSomething();
}

这是在Linux上。我没有使用C++0x,而是gcc v3.4.6。


2
取决于你是在Windows还是其他地方。 - bmargulies
取决于你是否使用boost库:http://drdobbs.com/cpp/184401518?pgno=6 - Anycorn
你需要提供更多关于实际平台的信息,包括你是否可以/想要使用C++0x特性(这些特性可能在你的平台上可用或不可用,因此平台很重要:操作系统、编译器和版本)。 - David Rodríguez - dribeas
9个回答

70
#include <boost/thread/tss.hpp>
static boost::thread_specific_ptr< MyClass> instance;
if( ! instance.get() ) {
    // first time called by this thread
    // construct test element to be used in all subsequent calls from this thread
    instance.reset( new MyClass);
}
    instance->doSomething();

10
没错,不必纠结于特定操作系统的内容,使用boost::thread_specific_ptr干净、可移植且符合惯用语。 - Alexandre C.
15
你说的“超级懒惰”是指那些看重精心构建的示例代码的人吗?=) - Claudiu
请注意,仅当退出线程为boost :: thread时,才会执行* instance的清理工作。 - jupp0r
4
原问题涉及“静态”线程局部变量,这正是通过C++的thread_local关键字内置的内容,所以他应该尽简单地使用C++的本地支持,例如thread_local MyClass instance;。本答案中的thread_specific_ptr代码更加复杂且不符合标准。虽然在此处不是正确的选择,但thread_specific_ptr仍可用于动态分配线程局部对象。 - user2913094
@user2913094,GCC 3.4毫不意外地没有这个功能。此外,GCC 4.6和MSVC <VS2015也没有为非平凡类型实现thread_local,这意味着您甚至无法在其中使用unique_ptr或shared_ptr。这意味着在当前编译器上,使用thread_local可能会导致内存泄漏。 - sehe

69

值得注意的是,C++11引入了thread_local关键字。

下面是来自存储期指定符的示例:

#include <iostream>
#include <string>
#include <thread>
#include <mutex>

thread_local unsigned int rage = 1; 
std::mutex cout_mutex;

void increase_rage(const std::string& thread_name)
{
    ++rage;
    std::lock_guard<std::mutex> lock(cout_mutex);
    std::cout << "Rage counter for " << thread_name << ": " << rage << '\n';
}

int main()
{
    std::thread a(increase_rage, "a"), b(increase_rage, "b");
    increase_rage("main");

    a.join();
    b.join();

    return 0;
}

可能的输出:

Rage counter for a: 2
Rage counter for main: 2
Rage counter for b: 2

2
GCC在4.8版本中增加了对thread_local的支持。如果您使用的是4.7或更早版本,则需要使用__thread来指定存储类。 - Drew Noakes
1
这正是我所需要的。但为什么需要mutex?因为thread_local是针对每个线程私有的吗? - user2269707
@reavenisadesk,你不需要为任何thread_local变量使用mutex - russoue
1
不幸的是,在OSX上让thread_local工作起来有点困难:https://dev59.com/ZF4c5IYBdhLWcg3wSIrf - Alex Jansen
5
@reavenisadesk,“mutex”仅同步输出,因为流不能一次性写入所有内容。如果没有锁定,您会读到类似“Rage counter for Rage counter for aRage counter for: main”的内容。 - Youka
显示剩余2条评论

14

boost::thread_specific_ptr是最好的选择,因为它是可移植的解决方案。

在Linux和GCC上,您可以使用__thread修饰符

因此,您的实例变量将如下所示:

static __thread MyClass *instance = new MyClass();

2
当使用__thread时,要注意MyClass的析构函数可能不会被执行。(boost::thread_specific_ptr<MyClass>或thread_local MyClass会执行析构函数。) - David L.

11
如果您正在使用Pthreads,您可以执行以下操作:
//declare static data members
pthread_key_t AnotherClass::key_value;
pthread_once_t AnotherClass::key_init_once = PTHREAD_ONCE_INIT;

//declare static function
void AnotherClass::init_key()
{
    //while you can pass a NULL as the second argument, you 
    //should pass some valid destrutor function that can properly
    //delete a pointer for your MyClass
    pthread_key_create(&key_value, NULL);
}

void AnotherClass::threadSpecificAction()
{
  //Initialize the key value
  pthread_once(&key_init_once, init_key);

  //this is where the thread-specific pointer is obtained
  //if storage has already been allocated, it won't return NULL

  MyClass *instance = NULL;
  if ((instance = (MyClass*)pthread_getspecific(key_value)) == NULL)
  {
    instance = new MyClass;
    pthread_setspecific(key_value, (void*)instance);
  }

  instance->doSomething();
}

4

C++11规定了一个thread_local存储类型,只需使用它即可。

AnotherClass::threadSpecificAction()
{
  thread_local MyClass *instance = new MyClass();
  instance->doSomething();
}

一个可选的优化是在线程本地存储上分配。


3
不过在 macOS 或 iOS 上尚未实现。 - jupp0r
@jupp0r 如果你的OS X编译器不支持C++11,那么它就不符合dcl.stc/1规范中的存储类说明符:static、thread_local、extern和mutable。 - rustyx
@rustyx:是的,截至目前,AppleClang不支持C++11(但在XCode8中将支持thread_local)。 - jupp0r

4

3
由于根据规则和限制,我不认为这个答案回答了问题,所以我会将其踩一下。它没有提到C2482C2483 - Andreas Haferburg
点赞,因为这个问题的标题没有提到Linux。搜索流量是由标题驱动的,而不是问题正文中一些微妙的免责声明。然而,那些链接是有用的。 - Contango

3
在Windows上,您可以使用TlsAllocTlsFree来分配线程本地存储中的存储空间。
要在TLS中设置和检索值,可以分别使用TlsSetValueTlsGetValue这里您可以看到一个示例,说明如何使用它。

2

顺便提一下... MSVC++从VSC++2005开始支持declspec(thread)

#if (_MSC_VER >= 1400)
  #ifndef thread_local     
    #define thread_local __declspec(thread)
  #endif
#endif

主要问题是(这在boost :: thread_specific_ptr中得到解决)标记为it的变量不能包含构造函数或析构函数。

1
这个问题在C++11中得到了解决,该版本受MSVC 2015(以及可能的2013)支持。 - rustyx

1

Folly (Facebook Open-source Library)提供了一个可移植的线程本地存储实现。

根据其作者所说:

针对非平凡类型进行了改进的线程本地存储(类似于pthread_getspecific的速度,但仅消耗单个pthread_key_t,比boost::thread_specific_ptr快4倍)。

如果您正在寻找可移植的本地存储线程实现,这个库是一个不错的选择。


没有Windows支持,虽然(便携性这么糟糕)。 - jupp0r

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接