线程局部存储TLS

进程和线程实现并发,它们各自都有优缺点。进程是因为具有独立的进程地址空间,所以在创建进程和销毁进程时需要额外的开销,但是由于进程间的数据是独立的,因此一般情况下当一个进程出现意外死去的时候,是不会影响到其它线程的。但是线程和父线程共享全局变量,它只具有独立的堆栈空间和寄存器,虽然在创建线程和销毁线程时需要的开销比进程小很多,但是一般情况下当一个线程意外死去的时候,会导致整个主线程的崩溃。但是TLS(thread local storage)为线程这一弊端提供了有效的解决办法。TLS简单的来说,就是有一个变量(通常是指针变量,指向一个数据类型),不同线程都有一个该变量的副本,那么当我们在不同线程使用该变量的时候就会获得不同的值。因此真能有效的解决不同线程中对共享全局变量的操作冲突的问题(当然也可以使用互斥锁之类的通信实现,不过这会存在当某个线程中对该全局变量操作不适当,引起其他线程崩溃的弊端)。TLS在不同的平台有不同的实现方法。我们先说说在Windows中的实现方法:

我们知道在Windows平台下,创建线程时,操作系统会提供一个结构来描述线程,这个结构叫做TEB((Thread Environment Block),每个线程都对应一个TEB,切换线程的时候,也会切换到不同的TEB。有某个指针值指向当前的TEB, 切换线程的时候就改变这个指针值,这样访问线程相关的数值,就可以统一从这个指针值找起。windows中,这个线程指针值放在fs寄存器。

TEB中存放的都是和进程有关的变量,其中有一个是TLS数组指针,该数组存储了线程的有关数据。当每个线程创建的时候,都有有一个TLS数组随之创建,我们可以通过Windows下的

DWORD WINAPI TlsAlloc(void);
函数获得TLS数组中哪个index可用,然后再调用TlsSetValue(),TlsGetValue(),和TlsFree()函数对该数组的索引进行操作,通过调用GetLastError()函数可以查看上述函数是否调用成功。下面是MSDN上的函数原型:

BOOL WINAPI TlsSetValue(
  _In_      DWORD dwTlsIndex,
  _In_opt_  LPVOID lpTlsValue
);

LPVOID WINAPI TlsGetValue(
  _In_  DWORD dwTlsIndex
);

BOOL WINAPI TlsFree(
  _In_  DWORD dwTlsIndex
);

那么为什么通过设置TLS数组就可以实现线程局部存储呢?因为我们在获取可用的index的时候,实际上是获得其在TEB结构中的偏移量,当我们转换线程的时候,指向线程的TEB指针也会随之转换,那么我们就可以实现对不同线程的TEB结构的同一偏移量的数据进行操作,也就实现了线程局部存储。


至于Linux下的线程局部存储就是另一种的实现方式了。Linux下提供线程局部存储的操作函数如下:

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);
从函数来看,Linux下应该是没有采用TLS数组的方法。它是定义一个pthread_key_t 类型的 key作为全局变量,它是通过调用pthread_key_create()函数创建该变量,其中pthread_key_create()的第二参数是一个清理函数,用来在线程释放该线程存储的时候被调用。该函数指针可以设成 NULL ,这样系统将调用默认的清理函数。其余三个函数通过字面意思就可以理解它们的用处了。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值