Я пытаюсь реализовать поточно-безопасный контейнер без блокировки, аналогичный std::vector, в соответствии с этим https://software.intel.com/en-us/blogs/2008/07/24/tbbconcurrent_vector-secrets-of-memory-organization
Из того, что я понял, чтобы предотвратить перераспределение и недействительность всех итераторов на всех потоках, вместо одного непрерывного массива, они добавляют новые смежные блоки.
Каждый добавляемый блок имеет размер возрастающих степеней 2, поэтому они могут использовать log (index), чтобы найти правильный сегмент, где должен находиться элемент в [index].
Из того, что я собираю, у них есть статический массив указателей на сегменты, поэтому они могут быстро получить к ним доступ, однако они не знают, сколько сегментов пользователь хочет, поэтому они сделали небольшую начальную, и если количество сегменты превышают текущий счет, они выделяют огромный и переключаются на использование этого.
Проблема заключается в том, что добавление нового сегмента невозможно в безопасном потоке без ограничений или, по крайней мере, я не понял, как это сделать. Я могу атомарно увеличивать текущий размер, но только это.
А также переход от малого к большому массиву указателей сегмента включает в себя большое выделение и копии памяти, поэтому я не могу понять, как они это делают.
У них есть код, размещенный в Интернете, но все важные функции не имеют исходного кода, они находятся в их DLL Building Building Blocks. Вот несколько примеров, которые демонстрируют проблему:
template<typename T>
class concurrent_vector
{
private:
int size = 0;
int lastSegmentIndex = 0;
union
{
T* segmentsSmall[3];
T** segmentsLarge;
};
void switch_to_large()
{
//Bunch of allocations, creates a T* segmentsLarge[32] basically and reassigns all old entries into it
}
public:
concurrent_vector()
{
//The initial array is contiguous just for the sake of cache optimization
T* initialContiguousBlock = new T[2 + 4 + 8]; //2^1 + 2^2 + 2^3
segmentsSmall[0] = initialContiguousBlock;
segmentsSmall[1] = initialContiguousBlock + 2;
segmentsSmall[2] = initialContiguousBlock + 2 + 4;
}
void push_back(T& item)
{
if(size > 2 + 4 + 8)
{
switch_to_large(); //This is the problem part, there is no possible way to make this thread-safe without a mutex lock. I don't understand how Intel does it. It includes a bunch of allocations and memory copies.
}
InterlockedIncrement(&size); //Ok, so size is atomically increased
//afterwards adds the item to the appropriate slot in the appropriate segment
}
};