Thread _ local 在 C + + 11中是什么意思?

我对 C + + 11中 thread_local的描述感到困惑。我的理解是,每个线程在函数中都有局部变量的唯一副本。所有线程都可以访问全局/静态变量(可能使用锁进行同步访问)。而且 thread_local变量对所有线程都是可见的,但是只能被定义它们的线程修改?对吗?

116377 次浏览

线程本地存储持续时间是一个术语,用于指代 看起来全局或静态存储持续时间的数据(从使用它的函数的角度来看) ,但实际上,每个 线。有一个副本

这增加了当前的选择:

  • 自动的(存在于块或函数中) ;
  • Static (在程序持续期间存在) ; 和
  • Dynamic (存在于分配和释放之间的堆上)。

线程本地的内容在线程创建时产生,并在线程结束时处理掉。


例如,考虑一个随机数生成器,其中种子必须在每个线程的基础上维护。使用线程本地种子意味着每个线程获得自己的随机数序列,独立于所有其他线程。

如果你的种子是随机函数中的一个局部变量,每次你调用它的时候它都会被初始化,每次给你相同的数字。如果它是一个全局的,那么线程将互相干扰对方的序列。


另一个例子是类似 strtok的东西,其中标记状态存储在特定于线程的基础上。这样,一个线程可以确保其他线程不会搞砸它的标记化工作,同时仍然能够在多次调用 strtok时维护状态——这基本上使得 strtok_r(线程安全版本)变得多余。


然而,另一个的例子应该类似于 errno。您不希望在其中一个调用失败之后,但是在您有机会检查结果之前,单独的线程修改 errno


此站点 对不同的存储持续时间说明符有合理的描述。

线程本地存储与静态(= 全局)存储一样存在于每个方面,只是每个线程都有对象的单独副本。对象的生命周期从线程开始(对于全局变量)或者首次初始化(对于块局部静态变量)开始,到线程结束(例如,当调用 join()时)结束。

因此,只有也可以声明为 static的变量才可以声明为 thread_local,即全局变量(更确切地说: “在名称空间范围内”的变量)、静态类成员和块静态变量(在这种情况下,隐含了 static)。

例如,假设您有一个线程池,想知道您的工作负载平衡得如何:

thread_local Counter c;


void do_work()
{
c.increment();
// ...
}


int main()
{
std::thread t(do_work);   // your thread-pool would go here
t.join();
}

这将打印线程使用统计数据,例如使用这样的实现:

struct Counter
{
unsigned int c = 0;
void increment() { ++c; }
~Counter()
{
std::cout << "Thread #" << std::this_thread::id() << " was called "
<< c << " times" << std::endl;
}
};

当声明一个变量 thread_local时,每个线程都有自己的副本。通过名称引用它时,将使用与当前线程关联的副本。例如:。

thread_local int i=0;


void f(int newval){
i=newval;
}


void g(){
std::cout<<i;
}


void threadfunc(int id){
f(id);
++i;
g();
}


int main(){
i=9;
std::thread t1(threadfunc,1);
std::thread t2(threadfunc,2);
std::thread t3(threadfunc,3);


t1.join();
t2.join();
t3.join();
std::cout<<i<<std::endl;
}

此代码将输出“2349”、“3249”、“4239”、“4329”、“2439”或“3429”,但不会输出其他任何内容。每个线程都有自己的 i副本,它被赋值、递增然后打印出来。运行 main的线程也有自己的副本,它在开始时被分配,然后保持不变。这些副本是完全独立的,每个副本都有不同的地址。

只有 姓名在这方面是特殊的-,如果你取 thread_local变量的地址,那么你只有一个指向普通对象的普通指针,可以在线程之间自由传递。例如:。

thread_local int i=0;


void thread_func(int*p){
*p=42;
}


int main(){
i=9;
std::thread t(thread_func,&i);
t.join();
std::cout<<i<<std::endl;
}

由于 i的地址被传递给线程函数,那么即使 ithread_local,也可以将属于主线程的 i的副本分配给它。这个程序将因此输出“42”。如果你这样做了,那么你需要注意的是在线程退出之后 *p不会被访问,否则你会得到一个迷途指针和未定义的行为,就像其他情况下指向对象被销毁一样。

thread_local变量在“首次使用前”被初始化,所以如果它们从未被给定的线程触及,那么它们就不一定被初始化。这样,编译器就可以避免为完全自包含且不涉及任何变量的线程构造程序中的每个 thread_local变量。例如:。

struct my_class{
my_class(){
std::cout<<"hello";
}
~my_class(){
std::cout<<"goodbye";
}
};


void f(){
thread_local my_class unused;
}


void do_nothing(){}


int main(){
std::thread t1(do_nothing);
t1.join();
}

在这个程序中有两个线程: 主线程和手动创建的线程。两个线程都不调用 f,因此从不使用 thread_local对象。因此,编译器是否将构造 my_class的0、1或2个实例是不确定的,并且输出可能是“”、“ hellohellogoodbyegoobye”或“ hellogoodbye”。