__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

你可能感兴趣的:(thread,null,delete,存储,化工,destructor)