源码地址
https://github.com/puzzzzzzle/algorithm_study/blob/master/algorithm_cpp/src/object_pool/ObjectPool.h
自动回收裸指针的对象池
原理
c++ shared_ptr, 在析构时, 会调用deleter 策略, 一般用于 共享指针中存储数组 我们可以在这里做文章, 自定义deleter, 按照一定的策略, 选择释放内存还是回收内存 注意, 这种方式无法回收共享内存本身, 但是可以做到自动回收 适用于构造和析构消耗较大 的对象获取对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ObjectT *AllocWithTrunk() {
for (int i = 0; i < KEEP_SIZE / 10; ++i) {
m_reusePool.Put(new ObjectT());
}
return new ObjectT();
}
ObjectPtrT Alloc() {
ObjectT *rowPtr = m_reusePool.Take();
if (rowPtr == nullptr) {
rowPtr = AllocWithTrunk();
}
assert(rowPtr != nullptr);
m_constructor(rowPtr);
return std::shared_ptr<ObjectT>(
rowPtr, [this](ObjectT *ptr) { ReleaseObject(ptr); });
}
从池中尝试获取一个, 没拿到的话就分配一批
目前的策略是分配最大池的大小的1/10, 可以调整分配的时候, 拿到裸指针后, 构建共享指针, 同时指定自定义的deleter
自动回收
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void ReleaseObject(ObjectT *ptr) {
bool needReuse = true;
if (m_isStopping) {
needReuse = false;
}
if (m_reusePool.Size() > KEEP_SIZE) {
needReuse = false;
}
m_destructor(ptr);
if (needReuse) {
m_reusePool.Put(ptr);
} else {
delete ptr;
}
}
当共享指针计数为0时, 会调用deleter, 回调到我们自定义的ReleaseObject中 首先调用已定义的析构策略 按照一定的策略决定要不要回收 不回收的直接delete 回收的 再次放回池子中
释放内存池
1
2
3
4
5
6
7
8
9
~ObjectPool() {
m_isStopping = true;
while (m_reusePool.Size() > 0) {
auto *rowPtr = m_reusePool.Take();
delete rowPtr;
}
}
释放对象池时, 要销毁所有对象, 释放池子中的对象 这时候要停止回收 我们设置 m_isStopping 为true, 这时候后续的都不会被回收
自定义策略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template <typename Object,
typename ReusePoolType = ThreadSafeQueuePoolType<Object *>,
size_t KEEP_SIZE_NUM = 10000,
typename Constructor = DefaultObjectConstructor<Object *>,
typename Destructor = DefaultObjectDestructor<Object *>>
class ObjectPool
Object: 要存储的数据类型 ReusePoolType: 用于存储池子中的对象, 默认是一个有锁的队列, 多线程安全性取决于它 KEEP_SIZE_NUM : 决定池子保存的对象的最大大小和每次分配量 Constructor : 从池子中获取一个对象前总会调用的策略 Destructor : 一个对象共享指针计数为0 时总会调用, 无论是否放回池子 构造和析构策略满足下面的格式就行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template <typename Object>
struct DefaultObjectConstructor {
using ObjectT = Object;
void operator()(ObjectT object) const {}
};
template <typename Object>
struct DefaultObjectDestructor {
using ObjectT = Object;
void operator()(ObjectT object) const {}
};
多线程安全
这个对象池除了 对象存储外都是多线程安全的
因此, 只要 ReusePoolType 是多线程安全的, 那池子本身就是安全的满足如下存储器策略即可
1.
1
2
3
4
5
6
template <typename Object>
struct PoolType {
void Put(ObjectT ptr) ;
ObjectT Take();
size_t Size();
};
默认提供一个基于锁的线程安全存储器, 无锁版本可以包装一个无锁队列就行
有需要的话可以使用 moodycamel 实现的无锁安全队列 https://github.com/cameron314/concurrentqueue1
2
3
4
5
6
7
8
9
template <typename Object>
struct ThreadSafeQueuePoolType
手动回收共享指针的对象池
线程安全性取决于 PoolType 是否是线程安全的 连 shared_ptr 也一同回收,但是需要手动调用回收函数,如果回收时, 引用计数不为1, 就会放弃回收 回收后, 原来的指针会被置为null 如果一个对象没有被 ReleaseObject 那就不会调用 Destructor, 而是直接使用默认的析构函数 需要保证析构函数和 Destructor 都可以做到资源释放, 且二次释放没有问题 用于 对象量极大, 连shared_ptr的构造和析构都是瓶颈的地方1
2
3
4
5
6
7
template <typename Object,
typename ReusePoolType =
ThreadSafeQueuePoolType<std::shared_ptr<Object>>,
size_t KEEP_SIZE_NUM = 10000,
typename Constructor = DefaultObjectConstructor<Object *>,
typename Destructor = DefaultObjectDestructor<Object *>>
class ObjectManulPool
手动回收
使用完毕后需要手动调用TryPushBack1
2
3
4
5
6
7
8
bool TryPushBack(ObjectPtrT &ptr) {
if (ptr.use_count() == 1) {
ReleaseObject(ptr);
ptr = nullptr;
return true;
}
return false;
}

