我对C++11中
thread_local
的描述感到困惑。我的理解是,每个线程在函数中都有唯一的局部变量副本。全局/静态变量可以被所有线程访问(可能使用锁同步访问)。 thread_local
变量对所有线程都可见,但只能由定义它们的线程修改?这是对的吗?
线程本地存储持续时间是一个术语,用于指代“看似”全局或静态存储持续时间的数据(从使用它的函数的角度来看),但实际上,每个线程都有一个副本。 它添加到当前选项:
自动(存在于块或函数期间);
例如,考虑一个随机数生成器,其中必须在每个线程的基础上维护种子。使用线程本地种子意味着每个线程都有自己的随机数序列,独立于所有其他线程。
如果您的种子是随机函数中的局部变量,则每次调用它时都会对其进行初始化,每次都会给您相同的数字。如果是全局的,线程会干扰彼此的序列。
另一个示例类似于
strtok
,其中标记化状态存储在特定于线程的基础上。这样,单个线程可以确保其他线程不会搞砸其标记化工作,同时仍然能够在多次调用
strtok
时保持状态 - 这基本上使 strtok_r
(线程安全版本)变得多余.
然而的例子类似于errno
。您不希望在某个调用失败之后、在您有机会检查结果之前,单独的线程修改
errno
。
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
的线程也有自己的副本,该副本在开始时被分配,然后保持不变。这些副本是完全独立的,并且每个副本都有不同的地址。只有 name
在这方面是特殊的 --- 如果您获取 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
变量。例如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();
}
在这个程序中有2个线程:主线程和手动创建的线程。两个线程都不会调用
f
,因此永远不会使用
thread_local
对象。因此,编译器是否会构造 0、1 或 2 个 my_class
实例是未指定的,输出可能是“”、“hellohellogoodbyegoodbye”或“hellogoodbye”。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;
}
};