1. 程式人生 > >tcmalloc原始碼閱讀(三)---ThreadCache分析之執行緒區域性快取

tcmalloc原始碼閱讀(三)---ThreadCache分析之執行緒區域性快取

執行緒區域性快取

tcmalloc採用執行緒區域性儲存技術為每一個執行緒建立一個ThreadCache,所有這些ThreadCache通過連結串列串起來。
執行緒區域性快取有兩種實現:
1. 靜態區域性快取,通過__thread關鍵字定義一個靜態變數。
2. 動態區域性快取,通過pthread_key_create,pthread_setspecific,pthread_getspecific來實現。

靜態區域性快取的優點是設定和讀取的速度非常快,比動態方式快很多,但是也有它的缺點。

主要有如下兩個缺點:

1. 靜態快取線上程結束時沒有辦法清除。
2. 不是所有的作業系統都支援。



ThreadCache區域性快取的實現

tcmalloc採用的是動態區域性快取,但同時檢測系統是否支援靜態方式,如果支援那麼同時儲存一份拷貝,方便快速讀取。
  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.   // Thread-specific key.  Initialization here is somewhat tricky
  21.   // because some Linux startup code invokes malloc() before it
  22.   // is in a good enough state to handle pthread_keycreate().
  23.   // Therefore, we use TSD keys only after tsd_inited is set to true.
  24.   // Until then, we use a slow path to get the heap object.
  25.   staticbool tsd_inited_;  
  26.   static pthread_key_t heap_key_;  
儘管在編譯器和聯結器層面可以支援TLS,但是作業系統未必支援,因此需要實時的檢查系統是否支援。主要是通過手動方式標識一些不支援的作業系統,程式碼如下:
thread_cache.h
  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. externbool kernel_supports_tls;   // defined in thread_cache.cc
  6. void CheckIfKernelSupportsTLS();  
  7. inlinebool 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.       } elseif (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.         elseif (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.       } elseif (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函式中
  1. void ThreadCache::InitTSD() {  
  2.   ASSERT(!tsd_inited_);  
  3.   perftools_pthread_key_create(&heap_key_, DestroyThreadCache);  
  4.   tsd_inited_ = true;  
  5. #ifdef PTHREADS_CRASHES_IF_RUN_TOO_EARLY
  6.   // We may have used a fake pthread_t for the main thread.  Fix it.
  7.   pthread_t zero;  
  8.   memset(&zero, 0, sizeof(zero));  
  9.   SpinLockHolder h(Static::pageheap_lock());  
  10.   for (ThreadCache* h = thread_heaps_; h != NULL; h = h->next_) {  
  11.     if (h->tid_ == zero) {  
  12.       h->tid_ = pthread_self();  
  13.     }  
  14.   }  
  15. #endif
  16. }  
該函式在TCMallocGuard的建構函式中被呼叫。TCMallocGuard類的宣告和定義分別在tcmalloc_guard.h和tcmalloc.cc檔案中。
  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<