__pool<true>的内存释放
多线程内存池__pool<true>的内存释放工作主要由函数_M_reclaim_block完成。
<mt_allocator.cc>
250 void
251 __pool<true>::_M_reclaim_block(char* __p, size_t __bytes)
函数原型,参数__p为用户释放的内存块地址,__bytes为内存块字节大小。
252 {
253 // Round up to power of 2 and figure out which bin to use.
254 const size_t __which = _M_binmap[__bytes];
255 const _Bin_record& __bin = _M_bin[__which];
找到负责的bin。
257 // Know __p not null, assume valid block.
258 char* __c = __p - _M_get_align();
259 _Block_record* __block = reinterpret_cast<_Block_record*>(__c);
记住用户数据区与内存块的首地址是有偏移的。
260 if (__gthread_active_p())
261 {
262 // Calculate the number of records to remove from our freelist:
263 // in order to avoid too much contention we wait until the
264 // number of records is "high enough".
根据注释,下面的代码采用了一种“转移策略”,在线程的空闲块个数达到一定“条件”的时候转移若干个块给全局空闲链表。读者可能还记得前面介绍过多线程下mt allocator转移数据块的原理,那里面说要把free计数器控制在used计数器的_S_freelist_headroom %以内,并且每次移动的块数为block_count个,block_count表示_S_chunk_size大小的内存能生成多少个空闲块。但是,现在我告诉你,这个算法已经“过时”了,至少下面的代码不是采用这个算法。
265 const size_t __thread_id = _M_get_thread_id();
266 const _Tune& __options = _M_get_options();
267 const unsigned long __limit = 100 * (_M_bin_size - __which)
268 * __options._M_freelist_headroom;
269
270 unsigned long __remove = __bin._M_free[__thread_id];
271 __remove *= __options._M_freelist_headroom;
272 if (__remove >= __bin._M_used[__thread_id])
273 __remove -= __bin._M_used[__thread_id];
274 else
275 __remove = 0;
276 if (__remove > __limit && __remove > __bin._M_free[__thread_id])
277 {
278 _Block_record* __first = __bin._M_first[__thread_id];
279 _Block_record* __tmp = __first;
280 __remove /= __options._M_freelist_headroom;
281 const unsigned long __removed = __remove;
这里计算出来的__removed,就是实际应该从线程里转移的块的个数。关于这个“转移策略”,我想在介绍完这段代码后专门研究,因为这里的空间显然不适合列举那些公式。
282 while (--__remove > 0)
283 __tmp = __tmp->_M_next;
284 __bin._M_first[__thread_id] = __tmp->_M_next;
285 __bin._M_free[__thread_id] -= __removed;
数出__removed个块,剩余的还由__bin._M_first[__thread_id]保管。
287 __gthread_mutex_lock(__bin._M_mutex);
288 __tmp->_M_next = __bin._M_first[0];
289 __bin._M_first[0] = __first;
290 __bin._M_free[0] += __removed;
291 __gthread_mutex_unlock(__bin._M_mutex);
把__removed个块加入到全局空闲链表里。注意这里需要给bin加锁,因为要修改bin里的全局数据。
292 }
293
294 // Return this block to our list and update counters and
295 // owner id as needed.
296 --__bin._M_used[__block->_M_thread_id];
__block指向的是用户现在归还的那个块,现在把分配这个块的线程的used计数器减1。由于内存块可能在线程间传递,所以__block->_M_thread_id可能不是当前线程的id。虽然我不是很肯定,但是这里显然使用了一个假设:第296行代码是原子的执行的。
298 __block->_M_next = __bin._M_first[__thread_id];
299 __bin._M_first[__thread_id] = __block;
300
301 ++__bin._M_free[__thread_id];
把用户释放的块加入当前线程的空闲链表里。
302