__mt_alloc源码分析(9)

来源:互联网 发布:mac安装office教程 编辑:程序博客网 时间:2024/06/06 02:57

__pool<true>的内存分

虽然我在前面已经介绍过__mt_alloc::allocate函数,但是结合这里的多线程环境,我建议读者还是回顾一下它的代码。其实__mt_alloc::allocate函数并不关心内存池是多线程还是单线程的,它在2种环境下都采用统一的操作步骤,只是在单线程的时候其中有些步骤什么事情都不用做,于是我们前面就忽略了。而到了多线程下,每个步骤都变得有用和必需了。

 

__pool<true>::_M_get_thread_id

首先我们遇到的多线程下内存池重要的操作步骤,是获得线程的id

 

<mt_allocator.cc>

581    size_t

582    __pool<true>::_M_get_thread_id()

 

函数原型,返回值就是调用_M_get_thread_id函数的线程id

 

583    {

584      // If we have thread support and it's active we check the thread

585      // key value and return its id or if it's not set we take the

586      // first record from _M_thread_freelist and sets the key and

587      // returns it's id.

588      if (__gthread_active_p())

 

检查是否链接了多线程库,如果没有的话,会当成单线程环境处理。

 

589        {

590     void* v = __gthread_getspecific(__gnu_internal::freelist._M_key);

591     size_t _M_id = (size_t)v;

 

访问线程私有数据,并转化成size_t类型的线程id。前面已经研究过,符号__gthread_getspecificPOSIX多线程库函数pthread_getspecific的弱引用,而后者的作用也在介绍freelist的时候也研究过了。

 

592     if (_M_id == 0)

 

如果得到的线程id0,说明从未给这个线程分配过id,那么开始id分配工作。

 

593       {

594         {

 

这个“{”括号是用来限定下面__gnu_cxx::lock的锁定范围的,和前面介绍_M_initialize时的那个括号一样。

 

595           __gnu_cxx::lock sentry(__gnu_internal::freelist_mutex);

596           if (__gnu_internal::freelist._M_thread_freelist)

 

检查是否有空闲的线程id

 

597         {

598           _M_id = __gnu_internal::freelist._M_thread_freelist->_M_id;

599           __gnu_internal::freelist._M_thread_freelist

600             = __gnu_internal::freelist._M_thread_freelist->_M_next;

 

得到头一个节点的线程id,然后把这个节点移出freelist。虽然这里再没有任何指针指向这个移出的节点,但是程序并没有内存泄漏,因为freelist._M_thread_freelist_array指向了整个节点数组,而且我们还可以通过线程id找到对应的节点。

 

601         }

602         }

603 

604         __gthread_setspecific(__gnu_internal::freelist._M_key,

605                   (void*)_M_id);

 

既然这个线程分配到了一个id,那么把它设置到线程私有数据里,这样以后就可以直接获得它了。有一个问题就是,如果线程id链表为空,没有获到线程id会怎样?那么这时候_M_id的值应该是0,于是整个_M_get_thread_id函数就会返回0。读者可以思考一下事情会变成怎样。

 

606       }

607     return _M_id >= _M_options._M_max_threads ? 0 : _M_id;

 

 

返回线程id。注意到这里作了比较,如果线程id超过了规定的线程个数上限,就返回0。我觉得这属于程序的策略,不同的人会有不同的意见。比如我初始化__mt_alloc<int>只支持10个线程,而其他类型不限制(默认是4096个),那么当有第11个线程要分配int对象的时候,我想让__mt_alloc<int>怎么做?有的人会认为,既然线程id4096个,为什么不忽略用户的“错误设置”,让__mt_alloc<int>正确工作呢?而另一部分人则会持相反看法,认为用户既然设置了这个参数,就不能被忽略。

显然__mt_alloc的作者同意后一部分人的想法,忠实的遵守用户的设置,即使是程序崩溃了,也要让用户来选择是对是错。

 

608        }

609 

610      // Otherwise (no thread support or inactive) all requests are

611      // served from the global pool 0.

612      return 0;

 

如果程序不支持多线程,直接返回0

 

613    }

 

__pool<true>::_M_adjust_freelist

得到了线程id__mt_alloc::allocate会在对应bin下对应的_M_first链表里,找寻空闲块,找到之后会交给用户使用,不过交出去之前还需要调整一些设置工作,这些工作是交给_M_adjust_freelist来做的。

 

337        void

338        _M_adjust_freelist(const _Bin_record& __bin, _Block_record* __block,

339              size_t __thread_id)

340        {

341     if (__gthread_active_p())

 

检查是否支持多线程,因为在单线程下这个函数什么事情也不需要做。

 

342       {

343         __block->_M_thread_id = __thread_id;

 

设置分配出去的块的头信息,主要是记录下分配线程的id。这是因为,如果块被传递给了其他线程,并由另一个线程释放的话,__pool<true>仍需要将分配线程的used计数器减1

 

344         --__bin._M_free[__thread_id];

345         ++__bin._M_used[__thread_id];

 

调整分配线程的usedfree计数器。

 

346       }

347        }