tcmalloc原始碼閱讀(三)---ThreadCache分析之執行緒區域性快取
阿新 • • 發佈:2019-02-14
執行緒區域性快取
tcmalloc採用執行緒區域性儲存技術為每一個執行緒建立一個ThreadCache,所有這些ThreadCache通過連結串列串起來。執行緒區域性快取有兩種實現:
1. 靜態區域性快取,通過__thread關鍵字定義一個靜態變數。
2. 動態區域性快取,通過pthread_key_create,pthread_setspecific,pthread_getspecific來實現。
靜態區域性快取的優點是設定和讀取的速度非常快,比動態方式快很多,但是也有它的缺點。
主要有如下兩個缺點:
1. 靜態快取線上程結束時沒有辦法清除。2. 不是所有的作業系統都支援。
ThreadCache區域性快取的實現
tcmalloc採用的是動態區域性快取,但同時檢測系統是否支援靜態方式,如果支援那麼同時儲存一份拷貝,方便快速讀取。- // If TLS is available, we also store a copy of the per-thread object
- // in a __thread variable since __thread variables are faster to read
-
// than pthread_getspecific(). We still need pthread_setspecific()
- // because __thread variables provide no way to run cleanup code when
- // a thread is destroyed.
- // We also give a hint to the compiler to use the "initial exec" TLS
- // model. This is faster than the default TLS model, at the cost that
-
// you cannot dlopen this library. (To see the difference, look at
- // the CPU use of __tls_get_addr with and without this attribute.)
- // Since we don't really use dlopen in google code -- and using dlopen
- // on a malloc replacement is asking for trouble in any case -- that's
- // a good tradeoff for us.
- #ifdef HAVE_TLS
- static __thread ThreadCache* threadlocal_heap_
- # ifdef HAVE___ATTRIBUTE__
- __attribute__ ((tls_model ("initial-exec")))
- # endif
- ;
- #endif
- // Thread-specific key. Initialization here is somewhat tricky
- // because some Linux startup code invokes malloc() before it
- // is in a good enough state to handle pthread_keycreate().
- // Therefore, we use TSD keys only after tsd_inited is set to true.
- // Until then, we use a slow path to get the heap object.
- staticbool tsd_inited_;
- static pthread_key_t heap_key_;
thread_cache.h
- // Even if we have support for thread-local storage in the compiler
- // and linker, the OS may not support it. We need to check that at
- // runtime. Right now, we have to keep a manual set of "bad" OSes.
- #if defined(HAVE_TLS)
- externbool kernel_supports_tls; // defined in thread_cache.cc
- void CheckIfKernelSupportsTLS();
- inlinebool KernelSupportsTLS() {
- return kernel_supports_tls;
- }
- #endif // HAVE_TLS
- thread_cache.cc
- #if defined(HAVE_TLS)
- bool kernel_supports_tls = false; // be conservative
- # if defined(_WIN32) // windows has supported TLS since winnt, I think.
- void CheckIfKernelSupportsTLS() {
- kernel_supports_tls = true;
- }
- # elif !HAVE_DECL_UNAME // if too old for uname, probably too old for TLS
- void CheckIfKernelSupportsTLS() {
- kernel_supports_tls = false;
- }
- # else
- # include <sys/utsname.h> // DECL_UNAME checked for <sys/utsname.h> too
- void CheckIfKernelSupportsTLS() {
- struct utsname buf;
- if (uname(&buf) != 0) { // should be impossible
- Log(kLog, __FILE__, __LINE__,
- "uname failed assuming no TLS support (errno)", errno);
- kernel_supports_tls = false;
- } elseif (strcasecmp(buf.sysname, "linux") == 0) {
- // The linux case: the first kernel to support TLS was 2.6.0
- if (buf.release[0] < '2' && buf.release[1] == '.') // 0.x or 1.x
- kernel_supports_tls = false;
- elseif (buf.release[0] == '2' && buf.release[1] == '.' &&
- buf.release[2] >= '0' && buf.release[2] < '6' &&
- buf.release[3] == '.') // 2.0 - 2.5
- kernel_supports_tls = false;
- else
- kernel_supports_tls = true;
- } elseif (strcasecmp(buf.sysname, "CYGWIN_NT-6.1-WOW64") == 0) {
- // In my testing, this version of cygwin, at least, would hang
- // when using TLS.
- kernel_supports_tls = false;
- } else { // some other kernel, we'll be optimisitic
- kernel_supports_tls = true;
- }
- // TODO(csilvers): VLOG(1) the tls status once we support RAW_VLOG
- }
- # endif // HAVE_DECL_UNAME
- #endif // HAVE_TLS
Thread Specific Key初始化
接下來看看每一個區域性快取是如何建立的。首先看看heap_key_的建立,它在InitTSD函式中- void ThreadCache::InitTSD() {
- ASSERT(!tsd_inited_);
- perftools_pthread_key_create(&heap_key_, DestroyThreadCache);
- tsd_inited_ = true;
- #ifdef PTHREADS_CRASHES_IF_RUN_TOO_EARLY
- // We may have used a fake pthread_t for the main thread. Fix it.
- pthread_t zero;
- memset(&zero, 0, sizeof(zero));
- SpinLockHolder h(Static::pageheap_lock());
- for (ThreadCache* h = thread_heaps_; h != NULL; h = h->next_) {
- if (h->tid_ == zero) {
- h->tid_ = pthread_self();
- }
- }
- #endif
- }
- class TCMallocGuard {
- public:
- TCMallocGuard();
- ~TCMallocGuard();
- };
- // The constructor allocates an object to ensure that initialization
- // runs before main(), and therefore we do not have a chance to become
- // multi-threaded before initialization. We also create the TSD key
- // here. Presumably by the time this constructor runs, glibc is in
- // good enough shape to handle pthread_key_create().
- //
- // The constructor also takes the opportunity to tell STL to use<