高并發內存池(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++中常用的"輸出參數"模式,特別適合需要返回多個值的場景。在內存池這種高性能組件中,這種設計確保了接口的效率和簡潔性。