muduo库的ThreadLocal类剖析

转载自: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的析构函数。

>>>第五部分,主线程中先打印两个全局对象名字,它们并没有改变。并且主线程结束时也调用了析构函数。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值