__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