线程局部存储

英文为Thread Local Storage,缩写为TLS。为什么要有TLS?原因在于, 全局变量与函数内定义的 静态变量,是各个线程都可以访问的共享变量。
中文名
线程局部存储
外文名
Thread Local Storage
缩    写
为TLS
含    义
全局变量与函数内定义的 静态变量

1简介编辑

在一个线程修改的内存内容,对所有线程都生效。这是一个优点也是一个缺点。说它是优点,线程的数据交换变得非常快捷。说它是缺点,一个线程死掉了,其它线程也性命不保; 多个线程访问共享数据,需要昂贵的同步开销,也容易造成同步相关的BUG。
如果需要在一个 线程内部的各个 函数调用都能访问、但其它线程不能访问的 变量(被称为static memory local to a thread 线程局部 静态变量),就需要新的机制来实现。这就是 TLS
线程局部存储在不同的平台有不同的实现,可移植性不太好。幸好要实现线程局部存储并不难,最简单的办法就是建立一个全局表,通过当前线程ID去查询相应的数据,因为各个 线程的ID不同,查到的数据自然也不同了。但Windows系统采用了每个线程建线程专享的索引表,表的条目为线程局部存储的地址。在线程执行的任何代码处,都可以查询本线程的这个索引表获得要访问的线程局部存储的地址。
大多数平台都提供了线程局部存储的方法,无需要我们自己去实现:

2linux实现编辑

int  pthread_key_create(pthread_key_t *key, void (*destructor)( void*));
int  pthread_key_delete(pthread_key_t key);
void *pthread_getspecific(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, const void * value);

3Win32实现编辑

方法一 (Kernel32 API)

每个 线程创建时系统给它分配一个 LPVOID 指针数组(叫做 TLS索引数组),这个数组从C编程角度是隐藏着的不能直接访问(实际上该数组地址写入了线程信息块 thread information block,缩写TIB或TEB),需要通过一些Kernel32  API函数调用访问。在进程内部创建、并发执行的各个线程,可以看作是执行相同动作(代码是一样的),但输入的数据不同,所以输出的结果数据也不同。因此各个线程使用的 数据结构是相同的,只是有些 变量是被所有的线程共享访问,为进程 全局变量;另外一些变量是由每个线程独享访问,即线程局部存储。而每个线程局部存储的地址需要存入该 线程TLS索引数组。
举例说明:设每个线程都要使用线程私有的一个 浮点型变量fvalue与一个长度为512个字节的缓冲区buf。需要在启动这些线程前,在主进程中先为fvalue与buf两个线程局部存储变量在TLS索引 数组申请两个条目,假设为fvalue申请到第3号条目,为buf申请到第5号条目。也就是说,在 任何一个线程内访问该线程私有的fvalue,需要查询该线程自己的TLS索引数组,其第3号条目存放的就是fvalue的地址。当然,启动各个线程后还需要为线程私有的fvalue与buf从堆中申请到 存储空间,然后把fvalue与buf的地址登记入该线程的TLS索引 数组的对应的第3号、第5号条目中,之后才能在该线程各处使用线程私有的fvalue与buf。
第一步,在主进程内调用TlsAlloc()函数,从将要启动的 每个线程的TLS索引数组中预定一个条目(slot),并返回该条目的序号:
DWORD global_dwTLS_fvalue =  TLSAlloc();
注意,此步之后, 变量( global_dwTLS_fvalue )保存的是分配得到的 TLS索引数组的某个条目的序号,例如值为3。编程者在写这个程序代码时规定了这个变量( global_dwTLS_fvalue )保存了线程局部存储fvalue在每个 线程的TLS索引 数组的对应条目的序号。变量( global_dwTLS_fvalue )是普通的 全局变量,各个线程随后只需要读取它的值。类似的,另外一个线程局部存储buf变量也需要定义一个变量( global_dwTLS_buf )并用TLSAlloc()初始化。
第二步,在每个进程执行的一开头,从堆中动态分配一块内存区域(使用 LocalAlloc() 函数调用
void* p_fvalue = LocalAlloc(LPTR,sizeof( float));
然后使用TlsSetValue()函数调用,把这块内存区域的地址存入TLS索引 数组相应的条目中:
TlsSetValue( global_dwTLS_fvalue, p_fvalue);
第三步,在每个 线程的任意执行位置,都可以通过该线程私有的TLS索引数组的相应条目,使用TlsGetValue()函数得到上一步的那块内存区域的地址,然后就可以对该内存区域做读写操作了。这就实现了在一个线程内部处处可访问的线程局部存储。
LPVOID lpvData = TlsGetValue(global_dwTLS_fvalue);
*lpvData = (float) 3.1416; //应用该线程局部存储
最后,如果不再需要上述 线程局部 静态变量,要动态释放掉这块内存区域(使用 LocalFree()函数),这一般在线程即将结束时清理线程占用的各项资源时释放。然后,主进程从TLS索引 数组中放弃对应的条目的占用(使用TlsFree()函数)。
LocalFree((HLOCAL) p_fvalue );
TlsFree(global_dwTLS_fvalue);

方法二

直接声明这个 变量是各个 线程有自己拷贝的线程局部 静态变量
__declspec( thread ) int var_name;
但在 Vista与Server 2008之前的操作系统,仅限于在应用程序的主进程(.exe)以及与主进程一起装入内存的 动态连接库(.dll),才能正常装入本方法所声明的线程 静态存储。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值