После переосмысления дизайна и некоторого вклада от Пэдди я придумал что-то вроде этого, но мне интересно, правильно ли это, когда я запускаю его, кажется, что все в порядке... Идея состоит в том, что предварительно распределенные объекты наследуются от следующего:
struct Node
{
void* pool;
};
Таким образом, мы вводим в каждый выделенный объект указатель на его пул для последующего его освобождения. Тогда у нас есть:
template<class T, int thesize>
struct MemPool
{
T* getNext();
void free(T* ptr);
struct ThreadLocalMemPool
{
T* getNextTL();
void freeTL();
int size;
vector<T*> buffer;
vector<int> freeList;
int freeListIdx;
int bufferIdx;
ThreadLocalMemPool* nextTlPool; //within a thread's context a linked list
};
int size;
threadlocal ThreadLocalMemPool* tlPool; //one of these per thread
};
Итак, в основном я говорю MemPool<Cat, 100>
, и это дает мне мемпул, который для каждого потока, который его getNexts
, будет создавать экземпляр локального мемпула потока. Размеры я округляю до ближайшей степени двойки для удобства по модулю (что для простоты я опускаю). Поскольку getNext()
является локальным для каждого потока, он не требует блокировки, и я пытаюсь использовать атомарные методы для освобождающей части следующим образом:
T* ThreadLocalMemPool::getNextTL()
{
int iHead = ++bufferIdx % size;
int iTail = freeListIdx % size;
if (iHead != iTail) // If head reaches tail, the free list is empty.
{
int & idx = freeList[iHead];
while (idx == DIRTY) {}
return buffer[idx];
}
else
{
bufferIdx--; //we will recheck next time
if (nextTLPool)
return nextTLPool->getNextTL();
else
//set nextTLPool to a new ThreadLocalMemPool and return getNextTL() from it..
}
}
void ThreadLocalMemPool::free(T* ptr)
{
//the outer struct handles calling this in the right ThreadLocalMemPool
//we compute the index in the pool from which this pool came from by subtracting from
//its address the address of the first pointer in this guys buffer
int idx = computeAsInComment(ptr);
int oldListIdx = atomic_increment_returns_old_value(freeListIdx);
freeList[oldListIdx % size] = idx;
}
Теперь идея состоит в том, что freeListIdx
всегда будет следовать за bufferIdx
в пуле, потому что вы не можете (я предполагаю правильное использование) освободить больше, чем вы выделили. Вызовы free синхронизируют порядок, в котором они возвращают буферные индексы в свободный список, и getNext уловит это при циклическом возврате. Я немного подумал об этом и не вижу ничего семантически неправильного в логике, кажется ли это правильным или есть что-то тонкое, что могло бы ее сломать?