tcmalloc源码阅读(三)---ThreadCache分析之线程局部缓存

线程局部缓存

tcmalloc采用线程局部存储技术为每一个线程创建一个ThreadCache,所有这些ThreadCache通过链表串起来。
线程局部缓存有两种实现:
1. 静态局部缓存,通过__thread关键字定义一个静态变量。
2. 动态局部缓存,通过pthread_key_create,pthread_setspecific,pthread_getspecific来实现。

静态局部缓存的优点是设置和读取的速度非常快,比动态方式快很多,但是也有它的缺点。

主要有如下两个缺点:

1. 静态缓存在线程结束时没有办法清除。
2. 不是所有的操作系统都支持。


ThreadCache局部缓存的实现

tcmalloc采用的是动态局部缓存,但同时检测系统是否支持静态方式,如果支持那么同时保存一份拷贝,方便快速读取。
[cpp]  view plain copy
  1. // If TLS is available, we also store a copy of the per-thread object  
  2.   // in a __thread variable since __thread variables are faster to read  
  3.   // than pthread_getspecific().  We still need pthread_setspecific()  
  4.   // because __thread variables provide no way to run cleanup code when  
  5.   // a thread is destroyed.  
  6.   // We also give a hint to the compiler to use the "initial exec" TLS  
  7.   // model.  This is faster than the default TLS model, at the cost that  
  8.   // you cannot dlopen this library.  (To see the difference, look at  
  9.   // the CPU use of __tls_get_addr with and without this attribute.)  
  10.   // Since we don't really use dlopen in google code -- and using dlopen  
  11.   // on a malloc replacement is asking for trouble in any case -- that's  
  12.   // a good tradeoff for us.  
  13. #ifdef HAVE_TLS  
  14.   static __thread ThreadCache* threadlocal_heap_  
  15. # ifdef HAVE___ATTRIBUTE__  
  16.    __attribute__ ((tls_model ("initial-exec")))  
  17. # endif  
  18.    ;  
  19. #endif  
  20.   
  21.   
  22.   // Thread-specific key.  Initialization here is somewhat tricky  
  23.   // because some Linux startup code invokes malloc() before it  
  24.   // is in a good enough state to handle pthread_keycreate().  
  25.   // Therefore, we use TSD keys only after tsd_inited is set to true.  
  26.   // Until then, we use a slow path to get the heap object.  
  27.   static bool tsd_inited_;  
  28.   static pthread_key_t heap_key_;  
尽管在编译器和连接器层面可以支持TLS,但是操作系统未必支持,因此需要实时的检查系统是否支持。主要是通过手动方式标识一些不支持的操作系统,代码如下:
thread_cache.h
[cpp]  view plain copy
  1. // Even if we have support for thread-local storage in the compiler  
  2. // and linker, the OS may not support it.  We need to check that at  
  3. // runtime.  Right now, we have to keep a manual set of "bad" OSes.  
  4. #if defined(HAVE_TLS)  
  5. extern bool kernel_supports_tls;   // defined in thread_cache.cc  
  6. void CheckIfKernelSupportsTLS();  
  7. inline bool KernelSupportsTLS() {  
  8.   return kernel_supports_tls;  
  9. }  
  10. #endif    // HAVE_TLS  
  11. thread_cache.cc  
  12. #if defined(HAVE_TLS)  
  13. bool kernel_supports_tls = false;      // be conservative  
  14. # if defined(_WIN32)    // windows has supported TLS since winnt, I think.  
  15.     void CheckIfKernelSupportsTLS() {  
  16.       kernel_supports_tls = true;  
  17.     }  
  18. # elif !HAVE_DECL_UNAME    // if too old for uname, probably too old for TLS  
  19.     void CheckIfKernelSupportsTLS() {  
  20.       kernel_supports_tls = false;  
  21.     }  
  22. # else  
  23. #   include <sys/utsname.h>    // DECL_UNAME checked for <sys/utsname.h> too  
  24.     void CheckIfKernelSupportsTLS() {  
  25.       struct utsname buf;  
  26.       if (uname(&buf) != 0) {   // should be impossible  
  27.         Log(kLog, __FILE__, __LINE__,  
  28.             "uname failed assuming no TLS support (errno)", errno);  
  29.         kernel_supports_tls = false;  
  30.       } else if (strcasecmp(buf.sysname, "linux") == 0) {  
  31.         // The linux case: the first kernel to support TLS was 2.6.0  
  32.         if (buf.release[0] < '2' && buf.release[1] == '.')    // 0.x or 1.x  
  33.           kernel_supports_tls = false;  
  34.         else if (buf.release[0] == '2' && buf.release[1] == '.' &&  
  35.                  buf.release[2] >= '0' && buf.release[2] < '6' &&  
  36.                  buf.release[3] == '.')                       // 2.0 - 2.5  
  37.           kernel_supports_tls = false;  
  38.         else  
  39.           kernel_supports_tls = true;  
  40.       } else if (strcasecmp(buf.sysname, "CYGWIN_NT-6.1-WOW64") == 0) {  
  41.         // In my testing, this version of cygwin, at least, would hang  
  42.         // when using TLS.  
  43.         kernel_supports_tls = false;  
  44.       } else {        // some other kernel, we'll be optimisitic  
  45.         kernel_supports_tls = true;  
  46.       }  
  47.       // TODO(csilvers): VLOG(1) the tls status once we support RAW_VLOG  
  48.     }  
  49. #  endif  // HAVE_DECL_UNAME  
  50. #endif    // HAVE_TLS  

Thread Specific Key初始化

接下来看看每一个局部缓存是如何创建的。首先看看heap_key_的创建,它在InitTSD函数中
[cpp]  view plain copy
  1. void ThreadCache::InitTSD() {  
  2.   ASSERT(!tsd_inited_);  
  3.   perftools_pthread_key_create(&heap_key_, DestroyThreadCache);  
  4.   tsd_inited_ = true;  
  5.   
  6.   
  7. #ifdef PTHREADS_CRASHES_IF_RUN_TOO_EARLY  
  8.   // We may have used a fake pthread_t for the main thread.  Fix it.  
  9.   pthread_t zero;  
  10.   memset(&zero, 0, sizeof(zero));  
  11.   SpinLockHolder h(Static::pageheap_lock());  
  12.   for (ThreadCache* h = thread_heaps_; h != NULL; h = h->next_) {  
  13.     if (h->tid_ == zero) {  
  14.       h->tid_ = pthread_self();  
  15.     }  
  16.   }  
  17. #endif  
  18. }  
该函数在TCMallocGuard的构造函数中被调用。TCMallocGuard类的声明和定义分别在tcmalloc_guard.h和tcmalloc.cc文件中。
[cpp]  view plain copy
  1. class TCMallocGuard {  
  2.  public:  
  3.   TCMallocGuard();  
  4.   ~TCMallocGuard();  
  5. };  
  6. // The constructor allocates an object to ensure that initialization  
  7. // runs before main(), and therefore we do not have a chance to become  
  8. // multi-threaded before initialization.  We also create the TSD key  
  9. // here.  Presumably by the time this constructor runs, glibc is in  
  10. // good enough shape to handle pthread_key_create().  
  11. //  
  12. // The constructor also takes the opportunity to tell STL to use  
  13. // tcmalloc.  We want to do this early, before construct time, so  
  14. // all user STL allocations go through tcmalloc (which works really  
  15. // well for STL).  
  16. //  
  17. // The destructor prints stats when the program exits.  
  18. static int tcmallocguard_refcount = 0;  // no lock needed: runs before main()  
  19. TCMallocGuard::TCMallocGuard() {  
  20.   if (tcmallocguard_refcount++ == 0) {  
  21. #ifdef HAVE_TLS    // this is true if the cc/ld/libc combo support TLS  
  22.     // Check whether the kernel also supports TLS (needs to happen at runtime)  
  23.     tcmalloc::CheckIfKernelSupportsTLS();  
  24. #endif  
  25.     ReplaceSystemAlloc();    // defined in libc_override_*.h  
  26.     tc_free(tc_malloc(1));  
  27.     ThreadCache::InitTSD();  
  28.     tc_free(tc_malloc(1));  
  29.     // Either we, or debugallocation.cc, or valgrind will control memory  
  30.     // management.  We register our extension if we're the winner.  
  31. #ifdef TCMALLOC_USING_DEBUGALLOCATION  
  32.     // Let debugallocation register its extension.  
  33. #else  
  34.     if (RunningOnValgrind()) {  
  35.       // Let Valgrind uses its own malloc (so don't register our extension).  
  36.     } else {  
  37.       MallocExtension::Register(new TCMallocImplementation);  
  38.     }  
  39. #endif  
  40.   }  
  41. }  
  42.   
  43.   
  44. TCMallocGuard::~TCMallocGuard() {  
  45.   if (--tcmallocguard_refcount == 0) {  
  46.     const char* env = getenv("MALLOCSTATS");  
  47.     if (env != NULL) {  
  48.       int level = atoi(env);  
  49.       if (level < 1) level = 1;  
  50.       PrintStats(level);  
  51.     }  
  52.   }  
  53. }  
  54. #ifndef WIN32_OVERRIDE_ALLOCATORS  
  55. static TCMallocGuard module_enter_exit_hook;  
  56. #endif  

线程局部缓存Cache的创建和关联

接下来看如何创建各个线程的ThreadCache的创建。我们看GetCache代码,该代码在do_malloc中被调用。
[cpp]  view plain copy
  1. inline ThreadCache* ThreadCache::GetCache() {  
  2.   ThreadCache* ptr = NULL;  
  3.   if (!tsd_inited_) {  
  4.     InitModule();  
  5.   } else {  
  6.     ptr = GetThreadHeap();  
  7.   }  
  8.   if (ptr == NULL) ptr = CreateCacheIfNecessary();  
  9.   return ptr;  
  10. }  
  11. void ThreadCache::InitModule() {  
  12.   SpinLockHolder h(Static::pageheap_lock());  
  13.   if (!phinited) {  
  14.     Static::InitStaticVars();  
  15.     threadcache_allocator.Init();  
  16.     phinited = 1;  
  17.   }  
  18. }  
该函数首先判断tsd_inited_是否为true,该变量在InitTSD中被设置为true。那么首次调用GetCache时tsd_inited_肯定为false,这时就InitModule就会被调用。InitModule函数主要是来进行系统的内存分配器初始化。如果tsd_inited_已经为true了,那么线程的thread specific就可以使用了,GetThreadHeap就是通过heap_key_查找当前线程的ThreadCache. 如果ptr为NULL,那么CreateCacheIfNecessary就会被调用,该函数来创建ThreadCache。
[cpp]  view plain copy
  1. ThreadCache* ThreadCache::CreateCacheIfNecessary() {  
  2.   // Initialize per-thread data if necessary  
  3.   ThreadCache* heap = NULL;  
  4.   {  
  5.     SpinLockHolder h(Static::pageheap_lock());  
  6.     // On some old glibc's, and on freebsd's libc (as of freebsd 8.1),  
  7.     // calling pthread routines (even pthread_self) too early could  
  8.     // cause a segfault.  Since we can call pthreads quite early, we  
  9.     // have to protect against that in such situations by making a  
  10.     // 'fake' pthread.  This is not ideal since it doesn't work well  
  11.     // when linking tcmalloc statically with apps that create threads  
  12.     // before main, so we only do it if we have to.  
  13. #ifdef PTHREADS_CRASHES_IF_RUN_TOO_EARLY  
  14.     pthread_t me;  
  15.     if (!tsd_inited_) {  
  16.       memset(&me, 0, sizeof(me));  
  17.     } else {  
  18.       me = pthread_self();  
  19.     }  
  20. #else  
  21.     const pthread_t me = pthread_self();  
  22. #endif  
  23.   
  24.   
  25.     // This may be a recursive malloc call from pthread_setspecific()  
  26.     // In that case, the heap for this thread has already been created  
  27.     // and added to the linked list.  So we search for that first.  
  28.     for (ThreadCache* h = thread_heaps_; h != NULL; h = h->next_) {  
  29.       if (h->tid_ == me) {  
  30.         heap = h;  
  31.         break;  
  32.       }  
  33.     }  
  34.   
  35.   
  36.     if (heap == NULL) heap = NewHeap(me);  
  37.   }  
  38.   
  39.   
  40.   // We call pthread_setspecific() outside the lock because it may  
  41.   // call malloc() recursively.  We check for the recursive call using  
  42.   // the "in_setspecific_" flag so that we can avoid calling  
  43.   // pthread_setspecific() if we are already inside pthread_setspecific().  
  44.   if (!heap->in_setspecific_ && tsd_inited_) {  
  45.     heap->in_setspecific_ = true;  
  46.     perftools_pthread_setspecific(heap_key_, heap);  
  47. #ifdef HAVE_TLS  
  48.     // Also keep a copy in __thread for faster retrieval  
  49.     threadlocal_heap_ = heap;  
  50. #endif  
  51.     heap->in_setspecific_ = false;  
  52.   }  
  53.   return heap;  
  54. }  
  55. ThreadCache* ThreadCache::NewHeap(pthread_t tid) {  
  56.   // Create the heap and add it to the linked list  
  57.   ThreadCache *heap = threadcache_allocator.New();  
  58.   heap->Init(tid);  
  59.   heap->next_ = thread_heaps_;  
  60.   heap->prev_ = NULL;  
  61.   if (thread_heaps_ != NULL) {  
  62.     thread_heaps_->prev_ = heap;  
  63.   } else {  
  64.     // This is the only thread heap at the momment.  
  65.     ASSERT(next_memory_steal_ == NULL);  
  66.     next_memory_steal_ = heap;  
  67.   }  
  68.   thread_heaps_ = heap;  
  69.   thread_heap_count_++;  
  70.   return heap;  
  71. }  
CreateIfNecessary创建一个ThreadCache对象,并且将该对象与当前线程的pthread_key_关联,同时添加到ThreadCache链表的头部。这里有个特别的情况需要说明,首次调用malloc所创建的ThreadCache对象没有和pthread_key_关联,只是添加到了ThreadCache链表中去了,程序可能还会在tsd_inited_为true之前多次调用malloc,也就会多次进入CreateCacheIfNecessary函数,这时函数中会去遍历ThreadCache链表,发现当前线程已经创建好的ThreadCache对象。

总结

1. 线程局部数据的实现可分为静态和动态两种。
2. tcmalloc以动态线程局部数据实现为主,静态为辅。
3. 通过全局静态对象的构造函数来创建Thread Specific Key。
4. 线程首次调用GetCache函数会触发线程专属的ThreadCache对象创建并与pthread_key_关联,添加到ThreadCache链表。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值