一、线程局部存储简介
线程局部存储简单来说就是将对象内存和线程关联在一起。对象在线程开始后分配内存,在线程结束时释放内存,每个对象相对于线程是独立的,并且不会相互干扰。
Windows和Linux都有各自的办法分配线程局部存储。
Windows跟线程局部存储相关的API
- TlsAlloc:分配未被使用的线程局部存储槽索引,这个索引是线程环境块中一个数组的索引,该数组就是用来存储线程相关的内存的
- TlsGetValue:根据索引获取数组的元素的值
- TlsSetValue:根据索引设置数组的元素的值
- TlsFree:负责释放索引
Linux跟线程局部存储相关的API
- pthread_key_create
- pthread_getspecific
- pthread_setspecific
- pthread_key_delete
二、thread_local说明符
从C++11开始终于有一种统一的标准从语言角度声明线程局部存储对象了。
基本语法:只需要在普通变量声明上添加thread_local说明符即可,被thread_local说明符声明的变量在行为上非常像静态变量,只不过多了线程的属性。它能够解决全局变量和静态变量在多线程程序中存在的问题
struct X {
thread_local static int i;
};
thread_local X a;
int main() {
thread_local X b;
}
解决的问题举例:errno的多线程安全问题
errno通常用于存储程序运行时上一次发生的错误,早期时它是一个静态变量,由于当时大部分程序都是单线程的所以这没有任何问题。但是到了多线程时代,如果线程A在某个时刻刚调用了一个函数正准备获取错误码,也就是这个时候,线程B执行的某个函数后修改了错误码,那么线程A接下来获取了错误码自然不是他真正想要的那个。
为了避免这种问题,标准将errno重新定义为线程独立的变量,为了实现这个定义就需要用到线程局部存储,所以在C++11之前errno都是一个静态变量,从C++11开始errno被改成了一个线程局部存储变量。
三、线程局部存储的内存地址
- 可以取地址并且传递地址,但意义不大
- 无法结合常量表达式
static int sv;
thread_local int tv;
int main() {
constexpr int* sp = &sv; //编译成功,sv的地址在编译时确定
constexpr int* tp = &tv; //编译失败,tv的地址在运行时确定
}
线程局部存储只定义了对象的生命周期,没有定义可访问性。也就是说我们可以获取线程局部存储变量的地址,并且将这个地址传递给其他线程,并且其他线程可以在生命周期内自由的使用其变量。
使用 & 计算线程局部存储变量的地址是运行时计算出来的,他不是一个常量,也就是说他无法和常量表达式结合。
四、线程局部存储对象的初始化和销毁
对于同一个线程中线程局部存储对象只会被初始化一次,即使被多次调用,有点类似静态变量只会在全局初始化一次。线程局部存储对象的销毁通常发生在线程销毁的时刻。
总结:在C++11标准出现之前,C++语言标准对多线程的支持是不完善的,无法创建现成局部存储对象就是其中的一个缺陷,C++11的推出解决了这个局面。

被折叠的 条评论
为什么被折叠?



