I have the following case (simplified):
/* Register objects living
and retrieve them on demand if the object is still alive on request.
The interface have to be concurrency-safe.
*/
class Registry
{
public:
void add( const std::shared_ptr<Thing>& thing )
{ m_index.emplace_back( thing );
std::shared_ptr<Thing> find( ThingId id )
{
auto find_it = m_index.id( id );
if( find_it != end( m_index ) )
{
// we can't remove the index safely (see http://software.intel.com/sites/products/documentation/doclib/tbb_sa/help/index.htm )
return find_it->second.lock(); // null if the object don't exist anymore
}
return nullptr;
}
private:
tbb::concurrent_unordered_map< ThingId, std::weak_ptr<Thing> > m_index;
};
// Concurrency safe too.
class Workspace
{
Registry m_registry;
std::unique_ptr<Thing> make_new_thing( ThingId id ); // not important
public:
std::shared_ptr<Thing> find( ThingId id ) { return m_registry.find(id); }
/* The goal here is to either retrieve the existing object,
or to create it.
*/
std::shared_ptr<Thing> find_or_create( ThingId id )
{
// HERE IS THE PROBLEM!!!
if( auto thing = m_registry.find( id ) )
return thing;
return make_new_thing();
}
};
// Concurrency-safe too.
class Editor
{
Workspace& m_workspace;
tbb::concurrent_unordered_set<std::shared_ptr<Thing>> m_things;
public:
void add_target( ThingId id )
{
m_things.push( m_workspace.find_or_create( id ) );
}
};
The context is important but let's focus on this part:
std::shared_ptr<Thing> find_or_create( ThingId id )
{
if( auto thing = m_registry.find( id ) )
return thing;
return make_new_thing();
}
Here if simultaneous calls were made for this function, simultaneous calls to make_new_thing() could happen, which is valid if the Thing don't have the same id, but not if it doesn't. We can't remove ids from the Registry because of the concurrent_unordered_map implementation, so we have no way to check if the object is being created.
All this suggests that in this case, a synchronization mechanism is required. However, if I use something like a work queue, then I will have to provide a future, which currently is locking, but even with future.then() the caller could potentially wait a long time.
What I want is to avoid locking (with a mutex) if possible, and no future (in this particular case).
Do you see any way to do it without locking?