高并发内存池(12)-ThreadCache回收内存
高并发内存池(12)-ThreadCache回收内存
代码如下:
// 释放对象时,链表过长时,回收内存回到中心缓存
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);
}
//还给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
是因为:
- 需要修改调用方的变量
- 需要返回两个值(链表头和尾)
- 语法简洁且类型安全
- 性能零开销
- 代码可读性好
这是C++中常用的"输出参数"模式,特别适合需要返回多个值的场景。在内存池这种高性能组件中,这种设计确保了接口的效率和简洁性。