__mt_alloc源碼分析(7)

__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_newtrue,則不需要額外的初始化工作,因爲所有的內存操作都通過newdelete來完成。

 

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初始化爲它的下標加10id是全局的),__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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章