概念

线程局部存储(Thread Local Storage,即TLS)用于将数据与一个正在执行的指定线程关联起来,以实现线程的私有数据存储.

进程中的全局变量和函数中的静态变量是各个线程都可以访问的共享变量,在一个线程进行修改,会影响到其他线程.虽然这样很方便数据的交换,但是不够安全,并且带来了很大的同步开销,也容易出bug.

TLS机制就是用来实现在一个线程内部可访问,其他线程无法访问的变量,即static memory local to a thread,线程局部静态变量,或Thread-Specific Data,TSD,线程特有数据 .

该机制在不同操作系统下的实现方式各不相同,对应的系统API也不相同,大多数操作系统都已实现.

linux实现

API如下:

1
2
3
4
5
6
#include <pthread.h>

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

其中:

  • pthread_key_create : 为线程局部存储创建一个新的key,该key为全局所有线程可见的,线程退出时会调用destructor释放分配的缓存.
  • pthread_key_delete : 删除一个键,删除后,该key占用的内存被释放,注销一个TSD(并不会检查是否有线程正在使用)
  • pthread_setspecific : 设置对应key的具体的数据
  • pthread_getspecific : 获取对应key的具体的数据

pthread_setspecific将一个变量的地址传入,与key关联;pthread_getspecific则获取到该地址,用于操作数据.

不同的线程中操作同一个key,不会冲突,不同线程使用同一个key得到的地址是各自之前set的值,互不影响.

如此,可以写一份线程代码,使用同一个key来多线程操作不同的数据.

使用pthread_setspecific为一个键指定新的数据时,线程必须释放原来的线程数据以回收空间.

由于删除一个键时,与该键关联的线程数据并不被释放,因此线程数据必须在释放键之前完成.

windows实现

windows将TLS进行分块,分块数为TLS_MINIMUM_AVAILABLE,该值默认为64:

1
2
// winnt.h
#define TLS_MINIMUM_AVAILABLE 64

API如下:

1
2
3
4
DWORD TlsAlloc();
BOOL TlsSetValue(DWORD dwTlsIndex, LPVOID lpTlsValue);
LPVOID TlsGetValue(DWORD dwTlsIndex)
BOOL TlsFree(DWORD dwTlsIndex)

其中:

  • TlsAlloc : 获得一个索引,如果调用成功,则得到一个索引值,否则返回TLS_OUT_OFF_INDEXES(0xffffffff)
  • TlsSetValue : 通过索引设置数据
  • TlsGetValue : 通过索引获取数据
  • TlsFree : 释放索引和内存块

TLS回调函数