2016-06-27 15 views
5

私は計算を行うC++ 11プログラムを持っており、それらの計算結果をキャッシュするのにstd::unordered_mapを使用しています。プログラムは複数のスレッドを使用し、共有のunordered_mapを使用して計算結果を保存して共有します。mutexで挿入をロックしているにもかかわらず、std :: unordered_mapとのデータ競合

unordered_mapの読み取りとSTLコンテナの仕様に基づいて

だけでなく、unordered_map thread safety、複数のスレッドで共有unordered_mapは、一度に1個のスレッドを一度に書き込みますが、多くの読者を扱うことができるようです。

したがって、std::mutexを使用して、insert()の呼び出しをマップにラップするので、たかだか1つのスレッドしか挿入されません。

しかし、私のfind()コールでは、私の読書からは、一度に読むことができるはずのスレッドのように、mutexはありません。しかし、時折、データレース(TSANで検出されたもの)を取得して、SEGVで自分自身を明らかにしています。データレースは、私が上に述べたinsert()find()コールを明確に指しています。

find()コールをミューテックスでラップすると、問題はなくなります。しかし、私はこのプログラムをできるだけ速くしようとしているので、同時読み込みをシリアル化したくありません。 (参考:gcc 5.4を使用しています)

なぜこのようなことが起こりますか? std::unordered_mapの並行性保証に関する私の理解は間違っていますか?

+7

ライターがいるので、すべての書き込みと読み取りの呼び出しに同期が必要なので、 'shared_mutex'が必要です。 – NathanOliver

+6

リンクされたスレッドの答えは、「* A。同時に複数のスレッドを読む、**または** * B。同じ時間に1つのスレッドを書くこと」。 **または**に注意してください。 – Pixelchemist

+6

あなたは仕様を間違って解釈しました。単一のライターに関係なく、複数のリーダーを許可しますが、複数のリーダーは許可しません。 NathanOliverのコメント – antlersoft

答えて

3

あなたはまだ作家を保つためにあなたの読者のためmutexを必要としますが、 1を共有する必要があります。 C++14は、あなたがこのようなスコープのロックstd::unique_lockstd::shared_lockと一緒に使用することができますstd::shared_timed_mutexがあります

using mutex_type = std::shared_timed_mutex; 
using read_only_lock = std::shared_lock<mutex_type>; 
using updatable_lock = std::unique_lock<mutex_type>; 

mutex_type mtx; 
std::unordered_map<int, std::string> m; 

// code to update map 
{ 
    updatable_lock lock(mtx); 

    m[1] = "one"; 
} 

// code to read from map 
{ 
    read_only_lock lock(mtx); 

    std::cout << m[1] << '\n'; 
} 
2

そのアプローチにはいくつかの問題があります。

最初に、std::unordered_mapはの2つの過負荷を持ちます.1つはconstであり、1つはありません。
私はのnon-constバージョンがマップを突然変異させるとは思っていませんが、複数のスレッドから非constメソッドを呼び出すコンパイラはデータ競合であり、実際には未定義の動作を使用するコンパイラ厄介な最適化のために。
まずは、複数のスレッドが起動するときに、std::unordered_map::findがconstバージョンで実行されることを確認する必要があります。これは、const参照でマップを参照してそこからfindを呼び出すことで実現できます。

2番目のスレッドでは、マップ上でconst findを呼び出す可能性がある部分は見逃していますが、他のスレッドはオブジェクトのconst以外のメソッドを呼び出すことはできません。私は間違いなく、多くのスレッドがfindと同時にinsertと呼んで、データ競合を引き起こしていると想像することができます。たとえば、insertはマップの内部バッファを再割り当てし、他のスレッドはそれを反復して必要なペアを見つけるとします。

これに対する解決策は、排他的/共有ロックモードを有するC++ 14 shared_mutexを使用することである。スレッドコールfindが呼び出されると、スレッドはinsertを呼び出して排他ロックでロックし、共有モードでロックをロックします。

コンパイラがshared_mutexをサポートしていない場合、Linuxではpthread_rwlock_t、WindowsではSRWLockなどのプラットフォーム固有の同期オブジェクトを使用できます。

インテルのスレッドビルディングブロックライブラリで提供されているようなロックフリーのハッシュマップを使用するか、MSVC並行実行時にconcurrent_mapを使用することもできます。実装自体はロックフリーのアルゴリズムを使用します。これにより、アクセスは常にスレッドセーフで高速になります。

関連する問題