高并发内存池(12)-ThreadCache回收内存

高并发内存池(12)-ThreadCache回收内存

代码如下:

C++ 复制代码
// 释放对象时,链表过长时,回收内存回到中心缓存
void ThreadCache::ListTooLong(FreeList& list, size_t size)
{
	void* start = nullptr;
	void* end = nullptr;

	list.PopRange(start, end, list.MaxSize());

	CentralCache::GetInstance()->ReleaseListToSpans(start, size);
}
C++ 复制代码
//还给Central的自由链表
void* PopRange(void*&start,void*&end,size_t n)
{
	assert(n <= _size);
	start = _freeList;
	end = start;

	for (size_t i = 0; i < n - 1; ++i)
	{
		end = NextObj(end);
	}

	_freeList = NextObj(end);
	NextObj(end) = nullptr;
	_size -= n;
}

PopRange函数中使用引用参数(void*& start, void*& end)是极其重要的设计选择,有以下几个关键原因:

1. 需要修改调用方的变量

复制代码
void* start_ptr = nullptr;
void* end_ptr = nullptr;
freeList.PopRange(start_ptr, end_ptr, 5);  // 需要修改start_ptr和end_ptr的值

如果没有引用

  • 函数内部修改的只是参数的副本
  • 调用方的变量不会被实际修改
  • 无法返回提取的内存块链表信息

2. 需要返回两个值

函数需要同时返回:

  • 批量链表的头指针start
  • 批量链表的尾指针end

C++函数只能直接返回一个值,所以必须通过参数返回另一个值。

替代方案对比:

方案1:使用引用参数(当前实现) ✅

复制代码
void PopRange(void*& start, void*& end, size_t n);
// 清晰,高效,常用

方案2:返回结构体 ❌

复制代码
struct RangeResult {
    void* start;
    void* end;
};
RangeResult PopRange(size_t n);
// 需要定义额外结构体,不够直观

方案3:使用指针参数 ❌

复制代码
void PopRange(void** start, void** end, size_t n);
// 语法复杂,容易出错

3. 性能零开销

引用在底层通常通过指针实现,但:

  • 语法更简洁:像操作普通变量一样
  • 类型安全:编译器会检查类型匹配
  • 无性能损失:与指针方案性能相同

4. 代码可读性

对比两种写法:

使用引用(清晰)

复制代码
void* start, *end;
freeList.PopRange(start, end, 5);
// 现在start和end包含了提取的链表

使用指针参数(复杂)

复制代码
void* start, *end;
freeList.PopRange(&start, &end, 5);
// 需要取地址,容易忘记&

5. 在内存池中的具体应用

复制代码
// CentralCache向ThreadCache提供内存
void CentralCache::FetchRangeObj(void*& start, void*& end, size_t n)
{
    // 需要修改start和end来返回内存块链表
    _freeList.PopRange(start, end, n);
    // 现在start和end包含了提取的内存块
}

如果不使用引用会怎样?

复制代码
// 错误版本:不使用引用
void PopRange(void* start, void* end, size_t n)
{
    start = _freeList;  // 这只是修改局部副本!
    // 调用方的变量不会被修改
}

// 调用代码
void* my_start, *my_end;
freeList.PopRange(my_start, my_end, 5);  // my_start和my_end仍然是nullptr!

总结

使用引用参数void*& start, void*& end是因为:

  1. 需要修改调用方的变量
  2. 需要返回两个值(链表头和尾)
  3. 语法简洁且类型安全
  4. 性能零开销
  5. 代码可读性好

这是C++中常用的"输出参数"模式,特别适合需要返回多个值的场景。在内存池这种高性能组件中,这种设计确保了接口的效率和简洁性。