文章目录
thread cache设计思路
thread cache:每一个线程都有一个独立的空间,当线程都要申请空间(小于256KB)时直接在线程内部申请,不需要加锁竞争。
根据之前设计的定长内存池可知,一条链表管理释放内存只能处理固定内存大小的情况
如:这个自由链表每个节点都是4字节,这条链表只能处理申请4字节空间的情况。
而实际线程申请的空间不一定是相同的所以这里需要多条链表来处理。多条链表用哈希桶来管理
但是哈希桶中桶个数为多少合适呢,假设以8字节为一个桶
这里选择每8字节为一个桶,如果要申请5字节内存,也向其提供8字节。
申请9字节提供16字节空间。(这样不可避免的造成了内存泄漏的问题)
这里我们单独设计类管理对齐规则。
因为在64位下一个指针为8字节,所以每个桶中链表节点大小至少要等于8字节
但是每8个字节一个桶,桶的个数太多了。所以采用分级分桶
整体控制在最多10%左右的内碎片浪费
[1,128] 8byte对齐 freelist[0,16)
[128+1,1KB] 16byte对齐 freelist[16,72)
[1KB+1,8KB] 128byte对齐 freelist[72,128)
[8KB+1,64KB] 1KB对齐 freelist[128,184)
[64KB+1,256KB] 8KB对齐 freelist[184,208)
这样的话256KB只需要208个桶(208个自由链表)可以接受。
#pragma once
#include<iostream>
#include<vector>
#include<time.h>
#include<assert.h>
using std::cout; using std::endl;
static const size_t MAX_BYTE = 126 * 1024;//如果线程申请超过126KB不能直接向ThreadCache申请空间
static const size_t NUMLIST = 208;//ThreadCache中哈希桶的个数
//获取自由链表下一个节点
void*& NextObj(void* obj) {
return *(void**)obj;
}
//管理切分好内存的自由链表
class FreeList {
public:
FreeList() :_freeList(nullptr) {}
void Push(void* obj) {
//头插
assert(obj);
NextObj(obj) = _freeList;
_freeList = obj;
}
void* Pop() {
//头删
assert(_freeList);
void* obj = _freeList;
_freeList = NextObj(obj);
}
bool Empty() { return _freeList == nullptr; }
private:
void* _freeList;
};
//计算ThreadCache中哈希桶的桶个数
class SizeClass {
public:
static inline size_t RoundUp(size_t size) {
if (size <= 128) return _RoundUp(size, 8);
else if (size <= 1024) return _RoundUp(size, 16);
else if (size <= 8 * 1024) return _RoundUp(size, 128);
else if (size < 64 * 1024) return _RoundUp(size, 1024);
else if (size < 256 * 1024) return _RoundUp(size, 8 * 1024);
else{
assert(false);
return -1;
}
}
//计算在哈希桶的那个位置
static inline size_t Index(size_t size) {
assert(size <= MAX_BYTE);
static int Group[4] = { 16,56,56,56 };
if (size <= 128) return _Index(size, 8);
else if (size <= 1024) return _Index(size - 128, 16) + Group[0];
else if (size <= 8 * 1024) return _Index(size - 1024, 128) + Group[1] + Group[0];
else if (size < 64 * 1024) return _Index(size - 8 * 1024, 1024) + Group[2] + Group[1] + Group[0];
else if (size < 256 * 1024) return _Index(size - 64 * 1024, 8 * 1024) + Group[3] + Group[2] + Group[1] + Group[0];
else {
assert(false);
return -1;
}
}
private:
static inline size_t _RoundUp(size_t size, size_t AlignNum) {
size_t AligSize = size;
if (size % AlignNum != 0) {
AligSize = (size / AlignNum + 1) * AlignNum;
}
return AligSize;
}
static inline size_t _Index(size_t size, size_t AlignNum) {
size_t Pos = 0;
if (size % AlignNum == 0) {
Pos = size / AlignNum - 1;
}
else {
Pos = size / AlignNum;
}
return Pos;
}
};
thread cache实现无锁访问(线程本地存储)
线程共享进程地址空间,每个线程有独立的栈。高并发内存池中每个线程都有独立的thread cache。
线程本地存储:是一种变量的储存方法,这个变量在线程内部是可访问的,但是其他线程不能访问到。(TLS)
window创建静态ThreadCacheTLS为static __declspec(thread) ThreadCache*
还要注意用static修饰,防止重复定义
C++高并发内存池 ThreadCache代码
1.公共头文件Commen.h
#pragma once
#include<iostream>
#include<vector>
#include<time.h>
#include<assert.h>
#include<thread>
using std::cout; using std::endl;
static const size_t MAX_BYTE = 126 * 1024;//如果线程申请超过126KB不能直接向ThreadCache申请空间
static const size_t NUMLIST = 208;//ThreadCache中哈希桶的个数
//获取自由链表下一个节点
void*& NextObj(void* obj) {
return *(void**)obj;
}
//管理切分好内存的自由链表
class FreeList {
public:
FreeList() :_freeList(nullptr) {}
void Push(void* obj) {
//头插
assert(obj);
NextObj(obj) = _freeList;
_freeList = obj;
}
void* Pop() {
//头删
assert(_freeList);
void* obj = _freeList;
_freeList = NextObj(obj);
return obj;
}
bool Empty() { return _freeList == nullptr; }
private:
void* _freeList;
};
//计算ThreadCache中哈希桶的桶个数
class SizeClass {
public:
static inline size_t RoundUp(size_t size) {
if (size <= 128) return _RoundUp(size, 8);
else if (size <= 1024) return _RoundUp(size, 16);
else if (size <= 8 * 1024) return _RoundUp(size, 128);
else if (size < 64 * 1024) return _RoundUp(size, 1024);
else if (size < 256 * 1024) return _RoundUp(size, 8 * 1024);
else{
assert(false);
return -1;
}
}
//计算在哈希桶的那个位置
static inline size_t Index(size_t size) {
assert(size <= MAX_BYTE);
static int Group[4] = { 16,56,56,56 };
if (size <= 128) return _Index(size, 8);
else if (size <= 1024) return _Index(size - 128, 16) + Group[0];
else if (size <= 8 * 1024) return _Index(size - 1024, 128) + Group[1] + Group[0];
else if (size < 64 * 1024) return _Index(size - 8 * 1024, 1024) + Group[2] + Group[1] + Group[0];
else if (size < 256 * 1024) return _Index(size - 64 * 1024, 8 * 1024) + Group[3] + Group[2] + Group[1] + Group[0];
else {
assert(false);
return -1;
}
}
private:
static inline size_t _RoundUp(size_t size, size_t AlignNum) {
size_t AligSize = size;
if (size % AlignNum != 0) {
AligSize = (size / AlignNum + 1) * AlignNum;
}
return AligSize;
}
static inline size_t _Index(size_t size, size_t AlignNum) {
size_t Pos = 0;
if (size % AlignNum == 0) {
Pos = size / AlignNum - 1;
}
else {
Pos = size / AlignNum;
}
return Pos;
}
};
2.ThreadCache.h
#pragma once
#include"Common.h"
class ThreadCache {
private:
FreeList _FreeList[NUMLIST];
public:
//申请释放空间
void* ApplySpace(size_t size) {
assert(size <= MAX_BYTE);
size_t AligSize = SizeClass::RoundUp(size);
//计算桶位置
size_t index = SizeClass::Index(AligSize);
if (!_FreeList[index].Empty()) {
return _FreeList[index].Pop();
}
else {
//thread cache没有内存向central cache要空间
return RequestFromCentralCache(index, AligSize);
}
}
void ReleaseSpace(void* ptr, size_t size) {
assert(size <= MAX_BYTE && ptr != nullptr);
size_t index = SizeClass::Index(size);//找到第几个桶
//将这个空间头插到自由链表上
_FreeList[index].Push(ptr);
}
//向中心缓存申请空间,先空开
void* RequestFromCentralCache(size_t index, size_t size) { return nullptr; }
};
//每个线程都会拥有自己的ThreadCache
static __declspec(thread) ThreadCache* tls_threadcache = nullptr;//用static修饰只在当前文件可见
3.线程申请独立的ThreadCache空间实现无锁(ConcurrentAlloc.h)
#pragma once
#include"Common.h"
#include"ThreadCache.h"
//线程调用申请ThreadCache空间
static void* ConcurrentAlloc(size_t size) {
//获取线程自己的ThreadCache
if (tls_threadcache == nullptr) {
tls_threadcache = new ThreadCache;
}
cout << std::this_thread::get_id()<<" "<<tls_threadcache<< endl;
return tls_threadcache->ApplySpace(size);
}
static void ConcurrentFree(void* ptr, size_t size) {
//释放时每个线程一定有tls_threadcache
assert(tls_threadcache != nullptr);
tls_threadcache->ReleaseSpace(ptr,size);
}
测试Window下静态TLS
#include"ConcurrentAlloc.h"
void Alloc() {
for (int i = 0; i < 10; i++) {
void* ptr = ConcurrentAlloc(6);
}
}
void Alloc2() {
for (int i = 0; i < 10; i++) {
void* ptr = ConcurrentAlloc(7);
}
}
void TestTLS() {
std::thread t1(Alloc);
t1.join();
std::thread t2(Alloc2);
t2.join();
}
int main()
{
TestTLS();
return 0;
}
如上图可知两个线程拥有不同的ThreadCache对象,且这两个对象相互独立,这两个线程申请ThreadCache空间时不需要加锁