template<typename T>
class ThreadLocal : noncopyable
{
public:
ThreadLocal()
{
MCHECK(pthread_key_create(&pkey_, &ThreadLocal::destructor));
}
~ThreadLocal()
{
MCHECK(pthread_key_delete(pkey_));
}
T& value()
{
T* perThreadValue = static_cast<T*>(pthread_getspecific(pkey_));
if (!perThreadValue)
{
T* newObj = new T();
MCHECK(pthread_setspecific(pkey_, newObj));
perThreadValue = newObj;
}
return *perThreadValue;
}
private:
static void destructor(void *x)
{
T* obj = static_cast<T*>(x);
typedef char T_must_be_complete_type[sizeof(T) == 0 ? -1 : 1];
T_must_be_complete_type dummy; (void) dummy;
delete obj;
}
private:
pthread_key_t pkey_;
};
类ThreadLocal实际上就是封装了线程特有数据,所以主要介绍下线程特有数据,了解了线程特有数据,该类就很简单了。简单起见,直接摘抄《Linux/UNIX系统编程手册》了,哈哈。。。
要使用线程特有数据,库函数执行的一般步骤如下:
- 函数创建一个键(key),用以将不同函数使用的线程特有数据项区分开来。调用函数pthread_key_create()可创建此“键”,且只需在首个调用该函数的线程中创建一次。键在创建时并未分配任何线程特有数据块。
- 调用pthread_key_create()还有另一个目的,即允许调用者指定一个自定义解构函数,用于释放为该键所分配的各个存储块。当使用线程特有数据的线程终止时,Pthreads API会自动调用此解构函数,同时将该线程的数据块指针作为参数传入。
- 函数会为每个调用者线程创建线程特有数据块。这一分配通过调用malloc()(或类似函数)完成,每个线程只分配一次,且只会在线程初次调用此函数时分配。
- 为了保存上一步所分配存储块的地址,会使用两个Pthreads函数:pthread_setspecific()和pthread_getspecific()。调用函数pthread_setspecific()实际上是对Pthreads实现发起这样的请求:保存该指针,并记录其与特定键(该函数的键)以及特定线程(调用者线程)的关联性。调用pthread_getspecific()所执行的是互补操作:返回之前所保存的、与给定键以及调用线程相关联的指针。如果还没有指针与特定的键及线程相关联,那么pthread_getspecific()返回NULL。函数可以利用这一点来判断自身是否是初次为某个线程所调用,若为初次,则必须为该线程分配空间。
在ThreadLocal类中,构造函数执行了上面的第1步和第2步,即创建key,并定义解构函数。在析构函数中删除键,在上面的4步中并没有提到。在成员函数value中,使用pthread_getspecific获取线程特有数据块,如果为NULL,为该线程分配空间。即上面的第3步。并使用pthread_setspecific保存该指针,之后再调用pthread_getspecific就可以直接获取该指针。即为上面的第4步。