3

I have many threads which share access to the same pool of objects, and some threads could remove objects, so I use std::vector<std::shared_ptr<T>> to hold pointers to the objects so that objects are released when no longer needed by any threads.

I keep the size of the pool constant (the maximum amount of objects is known at start) and just zero the pointers to "removed" objects.

With the deprecation of std::atomic_...<std::shared_ptr>, I have to use class template specialization std::atomic<std::shared_ptr>, which gets me back to std::atomic which doesn't have a copy constructor and therefore can't be stored in std::vector.

So, the only way is to use the following container to deal with this:

struct T {};
std::vector<std::unique_ptr<std::atomic<std::shared_ptr<T>>>> pool;

I understand that "We can solve any problem by introducing an extra level of indirection", but this seems to be "way too much"; this is not only extra pointers and memory allocations, this makes the code hard to write and read with so many indirections.

Is there a recommended way to store a pool of objects with C++ in a multi-threaded environment?

Or, should I get back to one of the simplest solutions like storing reference counters (or states like "released", in my case) in my objects (or in a parallel array) together with:

  1. Storing plain objects in the array, which is really not good to combine with polymorphic class hierarchies, produces hard-to manage code, and gives hard times to use lock-free approaches.
  2. Store plain pointers.

Am I missing some new approach for this, which comes together with moving to std::atomic<std::shared_ptr> to have this in a straight-forward way, reusing existing stuff from C++ and std?

Of course, I can still use std::shared_ptr<T> with mutexes and so on, but I want to have a solid solution with the smallest number of used components, and approach closer to a lock-free architecture (I know that std::atomic is not lock-free) as possible.

14
  • 3
    The question is unclear. If you "keep the size of the pool constant" then the code should be compiled without errors. Show us a minimal reproducible example of a not compiled code. Commented Feb 26 at 16:48
  • 6
    std::atomic<std::shared_ptr<T>> can be used in a vector, it's just that you can never cop/move/resize the vector larger. If you have a fixed size, then you can create the vector with all the elements populated and then you can modify each element as you want. Commented Feb 26 at 16:49
  • 3
    std::vector<std::atomic<std::shared_ptr<T>>> pool(n); should work without errors. We still need a minimal reproducible example that demonstrates errors. Commented Feb 26 at 18:12
  • 1
    re unique_ptr: std::make_unique<std::atomic<…>[]>(number_of_entries) simply calls new atomic<…>[number_of_entries], which just needs a valid default-constructor. Alternatively, std::deque also works if you need resize but no insert in the middle, just push_back, push_front, etc. Commented Feb 26 at 18:18
  • 1
    If you know before starting any threads, you can simply create a new data structure with the new size; or keep a non-atomic structure and transform it into an atomic one when the final size is clear. If you know only after launching threads, the entire point is moot since you cannot resize a vector atomically. So you need a mutex anyway Commented Feb 26 at 21:20

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.