转载自:http://blog.csdn.net/freeelinux/article/details/53431275
首先来看一个概念:线程特定数据
>>>在单线程程序中,我们经常用全局变量共享数据。多线程环境下,全部变量被所有线程所共有。
>>>但有时应用程序设计中有必要提供线程私有的全局变量,仅在某个线程中有效
>>>POSIX线程库通过维护一定的数据结构来解决这个问题,这些数据称之为线程特定数据(Thread-specific Data,或TSD)
>>>对于POD类型,可以用__thread来解决。
POSIX线程库通过四个函数操作线程特定数据,分别是pthread_key_create,pthread_key_delete,pthread_getspecific,pthread_setspecific。
create创建一个key,一旦一个线程创建了一个key,那么所有的线程也都有这个key。我们可以为特定的线程指定特定的数据,可以使用set指定,get获取。那么这些数据就是每个线程所私有的,这样不同的线程的key就指向了不同的数据。delete是删除这个key,不是删除数据,删除数据要在create的时候指定一个回调函数,由回调函数来销毁数据,这个数据是堆上的数据就可以销毁。
ThreadLocal类的视图如下:
分析如下:
#ifndef MUDUO_BASE_THREADLOCAL_H
#define MUDUO_BASE_THREADLOCAL_H
#include <muduo/base/Mutex.h> // MCHECK
#include <boost/noncopyable.hpp>
#include <pthread.h>
namespace muduo
{
template<typename T>
class ThreadLocal : boost::noncopyable
{
public:
ThreadLocal()
{
//构造函数中创建key,数据的销毁由destructor来销毁
MCHECK(pthread_key_create(&pkey_, &ThreadLocal::destructor));
}
~ThreadLocal()
{
//析构函数中销毁key
MCHECK(pthread_key_delete(pkey_));
}
//获取线程特定数据
T& value()
{
T* perThreadValue = static_cast<T*>(pthread_getspecific(pkey_)); //通过key获取线程特定数据
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_; //key的类型是pthread_key_t类型
};
}
#endif
muduo库在key上面使用的trick:
muduo库ThreadLocal类只有一个成员pkey_,它是一个pthread_key_t类型。但是我们发现,muduo库注册的destroy()函数直接将它的入口参数x指针强制转化为T类型指针。然后直接调用delete。这是什么原因呢?
在pthread_key_create()函数中,传入了一个&ThreadLocal::destructor的成员指针,当线成局部变量销毁时,如果传入的第二个参数不为NULL,系统将调用该函数取销毁实际的数据。由于是类成员函数,隐藏了一个指针是this指针,这时候void *x,x的地址实际上就是this的地址,也就是该对象的地址。
上面我们知道,ThreadLocal类只有一个成员,就是pthread_key_t类型成员pkey_(不是指针类型),我们可以再回头看pthread_key_create()函数,我们发现它传入的第一个参数正是pkey_的地址,也就是pthread_key_t*类型。所以我们在destructor()函数中使用
T* obj = static_cast<T*>(x);
delete obj;
由于pkey_是传入pthread_key_create()的第一个参数,所以它的地址就是实际数据存放的地址。而(void*)this == (void*)&pkey_,所以直接强制转化this指针为T*类型,然后以
T*类型的方式delete,就会真实释放线程局部存储的数据。
这种方法相当于使用一个写死已经分配好的地址,去存储真实数据。&pkey->数据,实际上跟普通的指针功能是一样的。不过对外可以表现为一个pthread_key_t类型,相当于起了一个键值名字,看起来好像使用键值寻找指向的数据一样。
下面有部分百度百科对这个的解释:http://baike.baidu.com/link?url=mEco9WH30o91pW57KVOB6oK8irHl5FIpG2m2gcEPrj2HyB5D4j1djgxcySdLQ-tg
测试用例如下:
#include <muduo/base/ThreadLocal.h>
#include <muduo/base/CurrentThread.h>
#include <muduo/base/Thread.h>
#include <boost/noncopyable.hpp>
#include <stdio.h>
class Test : boost::noncopyable
{
public:
Test()
{
printf("tid=%d, constructing %p\n", muduo::CurrentThread::tid(), this);
}
~Test()
{
printf("tid=%d, destructing %p %s\n", muduo::CurrentThread::tid(), this, name_.c_str());
}
const muduo::string& name() const { return name_; }
void setName(const muduo::string& n) { name_ = n; }
private:
muduo::string name_;
};
muduo::ThreadLocal<Test> testObj1;
muduo::ThreadLocal<Test> testObj2;
void print()
{
printf("tid=%d, obj1 %p name=%s\n",
muduo::CurrentThread::tid(),
&testObj1.value(),
testObj1.value().name().c_str());
printf("tid=%d, obj2 %p name=%s\n",
muduo::CurrentThread::tid(),
&testObj2.value(),
testObj2.value().name().c_str());
}
void threadFunc()
{
print();
testObj1.value().setName("changed 1");
testObj2.value().setName("changed 42");
print();
}
int main()
{
testObj1.value().setName("main one");
print();
muduo::Thread t1(threadFunc);
t1.start();
t1.join();
testObj2.value().setName("main two");
print();
pthread_exit(0);
}
它的输出是这样的:
我们来分析一下结果:
>>>第一部分:这是主线程中第一次执行的结果,主线程中试图利用obj1的value()方法取该线程特定数据,此时线程特定数据还没有创建,所以内部调用T()创建,然后主线程设置线程特定数据的name为"main one",同时调动打印,由于obj2没有调用value()方法,所以他的name是空的。
>>>第二部分,子线程中没有调用value()直接打印,所以name都为空。此处我们可以看到调用了构造函数,所以构造出的新的obj1,和obj2都没有名字,它们就是线程局部变量,地址也都不同于之前的全局对象的地址。
>>>第三部分,更改了局部对象的名字。
>>>第四部分,由于muduo库pthread_key_create的实现注册了destroy函数,所以子线程终止时,不仅释放key,同时会释放数据,也就是调用test的析构函数。
>>>第五部分,主线程中先打印两个全局对象名字,它们并没有改变。并且主线程结束时也调用了析构函数。