Answers:
线程本地存储持续时间是一个术语,用于指代看似全局或静态存储持续时间的数据(从使用该函数的功能的角度来看),但实际上,每个线程只有一个副本。
它添加到当前的自动变量(存在于块/功能中),静态变量(存在于程序期间)和动态变量(存在于分配和释放之间的堆中)。
线程本地化的某些东西在线程创建时就存在,并在线程停止时被丢弃。
以下是一些示例。
想想一个随机数生成器,必须在每个线程的基础上维护种子。使用线程本地种子意味着每个线程都获得自己的随机数序列,而与其他线程无关。
如果您的种子是随机函数中的局部变量,则每次调用它时都会对其进行初始化,从而每次都给您相同的数字。如果是全局变量,线程将相互干扰。
另一个例子是strtok
令牌化状态是在特定于线程的基础上存储的。这样,单个线程可以确保其他线程不会加重其标记化工作,同时仍然能够通过多次调用来维持状态strtok
-这基本上会提供strtok_r
(线程安全的版本)冗余。
这两个示例都允许线程局部变量存在于使用它的函数中。在预线程代码中,它只是函数中的静态存储持续时间变量。对于线程,将其修改为线程本地存储持续时间。
另一个例子是errno
。您不希望errno
在一个调用失败之后但在可以检查变量之前修改单独的线程,但是每个线程只希望有一个副本。
该站点对不同的存储期限说明符有合理的描述。
r
代表“可重入”,这与线程安全无关。的确,您可以使某些事情与线程本地存储一起安全地在线程中工作,但不能使它们重新进入。
strtok
应该调用其他函数。
while (something) { char *next = strtok(whatever); someFunction(next); // someFunction calls strtok }
声明变量时,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
为完全独立的线程构建程序中的每个变量,并且不涉及任何变量。例如
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”。
g()
呼叫的开始threadFunc
,那么输出将0304029
或对其它一些置换02
,03
和04
。也就是说,即使i
在创建线程之前将9分配给了线程,这些线程也会获得i
where 的新构造副本i=0
。如果i
为分配了thread_local int i = random_integer()
,则每个线程将获得一个新的随机整数。
02
,03
,04
,有可能像其他序列020043
线程本地存储在各个方面都像静态(=全局)存储一样,只是每个线程都具有对象的单独副本。对象的生命周期从线程启动(对于全局变量)或首次初始化(对于块局部静态变量)开始,并在线程结束(即何时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;
}
};
strtok
。strtok
即使在单线程环境中也会损坏。