1 作用:用来存储线程局部变量。此变量相当于线程是独占的,不与其它线程共享。
2 为什么需要线程局部变量?
为了解决全局变量或者静态变量在多线程操作中存在的问题,一个典型的例子就是errno. errno通常用于存储程序当中上一次发生的错误。早期它是一个静态变量,由于当时大多数程序是单线程,因此没有任何问题。但是到了多线程时代,这种errno就不能满足需求了。设想一下,线程A在某个时刻发生了错误,正想获取其错误码,这个时间线程B执行某个函数之后修改了这个错误码,那么线程A接下来获取到的错误码自然不是它真正想要的那个。这个时候就需要将errno设计成thread_local变量了。
3 如何验证thread_local变量是生效的?
也就是说如何验证某个变量是单独属于某个线程的,以下面代码为例:
#include <thread>
#include <vector>
std::vector<int> myVector = {1, 2, 3, 4, 5};
void func(uint32_t times)
{
while(--times > 0)
{
if(times % 2 != 0)
{
myVector.push_back(0);
}
else
{
myVector.pop_back();
}
}
printf("Finished.\r\n");
}
int main()
{
uint32_t times = 10000000;
std::thread t1(func, times);
std::thread t2(func, times);
t1.join();
t2.join();
return 0;
}
如上所示,此代码运行之后必定crash, 原因是t1和t2共享myVector变量, 但是myVector是非线程安全的,并且访问myVector并没有做互斥保护。运行的效果可能如下:
现在将代码修改一下,将myVector变量声明为thread_local变量:
#include <thread>
#include <vector>
thread_local std::vector<int> myVector = {1, 2, 3, 4, 5};//声明为thread_local变量
void func(uint32_t times)
{
while(--times > 0)
{
if(times % 2 != 0)
{
myVector.push_back(0);
}
else
{
myVector.pop_back();
}
}
printf("Finished.\r\n");
}
int main()
{
uint32_t times = 10000000;
std::thread t1(func, times);
std::thread t2(func, times);
t1.join();
t2.join();
return 0;
}
编译并运行:
可以看到程序正常运行,不会crash. 说明线程t1和t2操作的myVector是不同的对象,是各自一份的,所以不存在线程安全问题。
4 那么thread_local变量的实现原理是什么?
考虑以下代码:
#include <thread>
thread_local int var = 100;
int func1()
{
return var;
}
int main()
{
std::thread t(func1);
t.join();
return 0;
}
将上面代码作反汇编,查看func1的汇编代码如下 :
0000000000400cf8 <_Z5func1v>:
400cf8: 55 push %rbp
400cf9: 48 89 e5 mov %rsp,%rbp
400cfc: 64 8b 04 25 fc ff ff mov %fs:0xfffffffffffffffc,%eax
400d03: ff
400d04: 5d pop %rbp
400d05: c3 retq
重点关注这条汇编:
400cfc: 64 8b 04 25 fc ff ff mov %fs:0xfffffffffffffffc,%eax
这个汇编的意思如果用类似C语言的方式表达出来:eax = *(fs+0xfffffffffffffffc),其中0xfffffffffffffffc是-4的补码,即eax = *(fs-4), 即将fs寄存器往后编移4个字节的内容赋值给eax, 众所周知,eax一般用于存储返回值,那么eax中存储的即是100. 也就是代表*(fs-4)是100。 那么fs寄存器又是什么呢?在x86中,fs寄存器用于记录线程。当发生线程切换时,操作系统会更新fs的值,使其指向当前线程的TLS区域(Thread-Local Storage)。所以,可以看到,如果声明了thread_local变量,那么该变量实际存储在线程的TSL区域。那么TSL区域又实际上位于哪里呢?实际上,位于线程的上下文中。操作系统在创建不同时,会创建出对应的上下文。而不同的线程创建出来的上下文往往是不同的,所以对应的寄存器值fs值往往也是不同的。这样就巧妙地区分了不同线程的“私有”thread_local变量了。