次のケースがあります(簡略化):
/* 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 ) );
}
};
コンテキストは重要ですが、この部分に焦点を当てましょう。
std::shared_ptr<Thing> find_or_create( ThingId id )
{
if( auto thing = m_registry.find( id ) )
return thing;
return make_new_thing();
}
ここで、この関数に対して同時呼び出しが行われた場合、make_new_thing() への同時呼び出しが発生する可能性があります。これは、Thing が同じ ID を持っていない場合には有効ですが、そうでない場合には有効ではありません。concurrent_unordered_map の実装のため、レジストリから ID を削除することはできません。そのため、オブジェクトが作成されているかどうかを確認する方法がありません。
これはすべて、この場合、同期メカニズムが必要であることを示唆しています。ただし、作業キューのようなものを使用する場合は、現在ロックされているフューチャーを提供する必要がありますが、future.then() を使用しても、呼び出し元は長時間待機する可能性があります。
私が望むのは、可能であれば(ミューテックスを使用して)ロックを回避し、将来(この特定のケースでは)を回避することです。
ロックせずにそれを行う方法はありますか?