__pool<true>的初始化
__pool<true>的初始化工作同樣包括2個部分,對象構造和初始化。
356 explicit __pool()
357 : _M_bin(NULL), _M_bin_size(1), _M_thread_freelist(NULL)
358 { }
359
360 explicit __pool(const __pool_base::_Tune& __tune)
361 : __pool_base(__tune), _M_bin(NULL), _M_bin_size(1),
362 _M_thread_freelist(NULL)
363 { }
構造函數基本和__pool<false>一樣,除了多出一個_M_thread_freelist成員變量。注意_M_bin_size仍是被初始化爲1。
初始化函數_M_initialize是讓我覺得頭疼的函數之一,不僅僅是因爲它很長(159行),而且因爲它代碼縮進混亂,變量命名有歧義等等。所以我不得不對代碼進行一些“修整”工作,以便順利的閱讀,不過下面的示例代碼我還是保持原樣。
<mt_allocator.cc>
421 void
422 __pool<true>::_M_initialize()
函數_M_initialize的原型。前面有部分代碼和__pool<false>::_M_initialize一樣,所以不用詳細解釋。
423 {
424 // _M_force_new must not change after the first allocate(),
425 // which in turn calls this method, so if it's false, it's false
426 // forever and we don't need to return here ever again.
427 if (_M_options._M_force_new)
428 {
429 _M_init = true;
430 return;
431 }
如果_M_force_new爲true,則不需要額外的初始化工作,因爲所有的內存操作都通過new和delete來完成。
433 // Create the bins.
434 // Calculate the number of bins required based on _M_max_bytes.
435 // _M_bin_size is statically-initialized to one.
436 size_t __bin_size = _M_options._M_min_bin;
437 while (_M_options._M_max_bytes > __bin_size)
438 {
439 __bin_size <<= 1;
440 ++_M_bin_size;
441 }
計算bin的個數,存放在_M_bin_size裏。
443 // Setup the bin map for quick lookup of the relevant bin.
444 const size_t __j = (_M_options._M_max_bytes + 1) * sizeof(_Binmap_type);
445 _M_binmap = static_cast<_Binmap_type*>(::operator new(__j));
446 _Binmap_type* __bp = _M_binmap;
447 _Binmap_type __bin_max = _M_options._M_min_bin;
448 _Binmap_type __bint = 0;
449 for (_Binmap_type __ct = 0; __ct <= _M_options._M_max_bytes; ++__ct)
450 {
451 if (__ct > __bin_max)
452 {
453 __bin_max <<= 1;
454 ++__bint;
455 }
456 *__bp++ = __bint;
457 }
創建和初始化_M_binmap。
459 // Initialize _M_bin and its members.
460 void* __v = ::operator new(sizeof(_Bin_record) * _M_bin_size);
461 _M_bin = static_cast<_Bin_record*>(__v);
分配bin數組。
463 // If __gthread_active_p() create and initialize the list of
464 // free thread ids. Single threaded applications use thread id 0
465 // directly and have no need for this.
466 if (__gthread_active_p())
函數__gthread_active_p在以前詳細討論過,在研究__common_pool_base的時候。它通過檢測庫函數pthread_cancel是否存在,來判斷是否鏈接了POSIX多線程庫。
467 {
468 {
這個“{”括號的作用是限制下面的__gnu_cxx::lock的作用域。
469 __gnu_cxx::lock sentry(__gnu_internal::freelist_mutex);
freelist_mutex
這裏又出現了幾個新詞彙,爲了弄懂它們,我會穿插其他代碼。我從freelist_mutex開始研究:
<mt_allocator.cc>
60 static __freelist freelist;
61 static __glibcxx_mutex_define_initialized(freelist_mutex);
__freelist是定義在__gnu_internal名字空間裏的一個類,freelist_mutex是它的鎖對象。在我的運行環境裏,宏__glibcxx_mutex_define_initialized被定義爲:
< libstdc++-v3/include/bits/concurrence.h>
46 # define __glibcxx_mutex_define_initialized(NAME) /
47 __gthread_mutex_t NAME = __GTHREAD_MUTEX_INIT
__gthread_mutex_t的定義則是在如下位置:
<gthr-default.h>
46 typedef pthread_key_t __gthread_key_t;
47 typedef pthread_once_t __gthread_once_t;
48 typedef pthread_mutex_t __gthread_mutex_t;
49 typedef pthread_mutex_t __gthread_recursive_mutex_t;
總結起來就是,freelist_mutex是一個pthread_mutex_t對象,並且被初始化爲__GTHREAD_MUTEX_INIT:
static pthread_mutex_t freelist_mutex = __GTHREAD_MUTEX_INIT;
freelist
那麼freelist又是什麼呢?答案是:它是真正的線程id鏈表!那麼__pool<true>的成員變量_M_thread_freelist呢?什麼作用也沒有,一直保持着NULL的值。看到這裏也許讀者有些迷惑了,其實我也迷惑過,所以纔會在開始的時候對函數_M_initialize那麼“不滿”。
現在我們忘記__pool<true>::_M_thread_freelist成員變量,只記得線程id鏈表是一個__freelist類型的全局靜態對象freelist,看看它的實現吧。
<mt_allocator.cc>
40 #ifdef __GTHREADS
__freelist的定義也是被這個條件宏包裹起來的,和__pool<true>的一樣。
41 struct __freelist
42 {
43 typedef __gnu_cxx::__pool<true>::_Thread_record _Thread_record;
存儲在__freelist鏈表裏的數據類型是_Thread_record,即線程id的節點。
44 _Thread_record* _M_thread_freelist;
45 _Thread_record* _M_thread_freelist_array;
46 size_t _M_max_threads;
要弄清楚這3個成員變量的意義還真不容易,至少我想了很久才明白。
_M_thread_freelist是線程id的鏈表,每個節點表示1到_M_max_threads之間的一個id。
_M_thread_freelist_array是一個_Thread_record數組的首地址。可以想到,如果我事先知道線程id鏈表的長度,那麼我不會一個一個分配每個節點,而是一次分配所有節點的內存,然後把它們“串聯”成一個鏈表。於是這些節點本質上就是一個數組。把數組首地址存儲在_M_thread_freelist_array裏,我就可以通過下標訪問到對應的節點。後面可以看到,通過把每個節點的線程id初始化爲它的下標加1(0號id是全局的),__freelist可以通過給定的線程id訪問到對應的節點,這在歸還線程id的時候是很有用的。
_M_max_threads是當前線程id鏈表的長度。初次看到這個成員變量的時候,我想不通它的作用是什麼。爲了防止讀者和我有同樣的疑問,我可以提前告訴讀者,它是__per_type_pool_policy下很重要的“功臣”。
47 __gthread_key_t _M_key;
在介紹freelist_mutex的時候,我貼出了定義__gthread_key_t的代碼,它實際上是pthread_key_t類型,用來操作線程私有數據,一個很好的指南位於:
http://www.ibm.com/developerworks/cn/linux/thread/posix_threadapi/part2/
簡單的說,首先整個程序調用一次函數pthread_key_create,它會開闢出一塊空間,並把一個給定的pthread_key_t類型對象初始化爲這塊空間的索引。然後每個線程都可以通過調用函數pthread_setspecific,以這個pthread_key_t類型對象爲參數,向這個空間填寫自己的私有數據(以void *的形式),而且各個線程間的數據互不干擾。如果要取出這些私有數據,各個線程只需調用函數pthread_getspecific,同樣以這個pthread_key_t類型對象爲參數。
在調用函數pthread_key_create的時候,還可以傳一個函數指針,類型爲:
void (*destructor)(void*)
這個destructor指針是一個“清理函數”,它在線程退出的時候被調用,清理存儲在這個pthread_key_t類型對象下的線程私有數據。這個函數需要一個參數,就是線程的私有數據(以void *的形式)。如果線程退出時私有數據爲NULL,則不調用清理函數。當然,如果沒有設置destructor,也不會有清理函數被調用。
49 ~__freelist()
50 {
51 if (_M_thread_freelist_array)
52 {
53 __gthread_key_delete(_M_key);
整個程序退出的時候,銷燬_M_key以及所有的線程私有數據。前面已經介紹過,類似於__gthread_xxx的符號都會變成對應函數pthread_xxx的弱引用,即如果函數pthread_xxx存在,那麼鏈接的時候__gthread_xxx就是函數地址;如果不存在,那麼__gthread_xxx就是0。
54 ::operator delete(static_cast<void*>(_M_thread_freelist_array));
釋放所有的線程id節點,記住它們是一個數組。
55 }
56 }
57 };
58
59 // Ensure freelist is constructed first.
60 static __freelist freelist;
61 static __glibcxx_mutex_define_initialized(freelist_mutex);
這2個全局靜態變量供整個程序的內存池使用。freelist_mutex的類型前面已經介紹過了。還有一點需要說明,freelist對象的所有成員變量都會初始化爲0,這是在鏈接的時候發生的。
63 static void
64 _M_destroy_thread_key(void* __id)
這個函數就是註冊給_M_key的“清理函數”,而參數__id則是每個線程通過pthread_setspecific設置的自己的線程id。在線程退出時,只要__id不爲0,都會調用_M_destroy_thread_key進行清理工作,於是freelist就可以在這個時候回收這個id。
65 {
66 // Return this thread id record to the front of thread_freelist.
67 __gnu_cxx::lock sentry(__gnu_internal::freelist_mutex);
鎖定整個freelist。新單詞__gnu_cxx::lock還沒有研究到,不過它的意義很明顯,就是鎖住freelist對象。
68 size_t _M_id = reinterpret_cast<size_t>(__id);
真正得到線程的id。__id的類型是void *,此處進行強制類型轉換,成爲size_t。
70 using namespace __gnu_internal;
71 typedef __gnu_cxx::__pool<true>::_Thread_record _Thread_record;
72 _Thread_record* __tr = &freelist._M_thread_freelist_array[_M_id - 1];
通過線程id,找到節點的地址。正如我前面介紹_M_thread_freelist_array的作用時所說,整個線程id鏈表在物理上其實是一個數組,而每個節點的線程id又被設置成下標值加1,所以這裏可以直接用_M_thread_freelist_array[_M_id - 1]得到節點的地址。
73 __tr->_M_next = freelist._M_thread_freelist;
74 freelist._M_thread_freelist = __tr;
把線程id節點加入到_M_thread_freelist鏈表裏。
75 }
76 #endif