当你声明一个变量为 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 的地址传递给线程函数,因此属于主线程的 i 的副本可以被分配,即使它是 thread_local 的。 因此,该程序将输出“42”。 如果您这样做,则需要注意,在所属线程退出后不要访问 *p,否则您将获得悬空指针和未定义行为,就像任何其他指向对象已销毁的情况一样。
thread_local 变量在“第一次使用”之前进行初始化,因此如果某个线程从未触及它们,则不一定会初始化。 这是为了允许编译器避免为完全独立且不接触任何 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
对象从未被使用。因此,编译器将构造0、1或2个
my_class
实例是未指定的,输出结果可能是 ""、"hellohellogoodbyegoodbye" 或 "hellogoodbye"。