2013-11-01 11 views
6

私は、マルチスレッドアプリケーションのどこからでもアクセスできる設定クラスをいくつか書いています。これらの設定は非常に頻繁に読み込まれるため(読み込みアクセスは高速にする必要があります)、あまり頻繁には書き込まれません。それはboost::atomicのように見えるプリミティブデータ型のスレッドセーフな設定

は私が必要なものを提供していますので、私はこのような何かを思い付いた:

class UInt16Setting 
{ 
    private: 
     boost::atomic<uint16_t> _Value; 
    public: 
     uint16_t getValue() const { return _Value.load(boost::memory_order_relaxed); } 
     void setValue(uint16_t value) { _Value.store(value, boost::memory_order_relaxed); } 
}; 

質問1:メモリ順序についてイムわかりません。私のアプリケーションでは、メモリの順序付けにはまったく関心がありません(私はそうですか?)。 getValue()が常に破損していない値(古いものか新しいものか)を返すことを確認したいだけです。私のメモリの注文設定は正しいですか?

質問2:このアプローチでは、この種の同期にはboost::atomicをお勧めしますか?または、より良い読み込みパフォーマンスを提供する他の構造体がありますか?

私のアプリケーションには、std::stringなどのより複雑な設定タイプ、またはたとえばboost::asio::ip::tcp::endpointのリストが必要です。これらの設定値はすべて不変であると考えています。ですから、setValue()を使用して値を設定すると、値自体(std::stringまたはエンドポイント自体のリスト)はもう変更されません。だからもう一度、私は古い値か新しい値のどちらかを取得するが、何らかの破損した状態を取得しないようにしたい。

質問3:このアプローチはboost::atomic<std::string>で動作しますか?そうでない場合は、何が選択肢ですか?

質問4:エンドポイントのリストのようなより複雑な設定タイプはどうですか? boost::atomic<boost::shared_ptr<std::vector<boost::asio::ip::tcp::endpoint>>>のようなものをお勧めしますか?そうでない場合、何が良いでしょうか?

+1

必要に応じて、複数のスレッドがこれらの設定を読み書きできるようにする必要性を再確認します。スレッドがgetBlahSetting()を呼び出して1つの結果を取得するのは意味があります。同じ関数で、後でもう一度呼び出すと、別の結果_が得られますか?私はあなたがハード同期ポイントで設計することをお勧めします。 – David

+3

設定はユーザーのために変更する必要があります。ユーザーは複数のことを同時に行うことはほとんどありません。それでも、作家は二人以上ではいけません。したがって、私は頻繁に起こるので、何の同期もせずにconfigにアクセス(=読み込み)します。設定が変更されるというまれなケースのために、変更スレッドから詳細なコピーを作成します(これは再び同期化する必要はありません)。次に、誰もが使用する設定オブジェクトへのポインタへの 'seq_cst'ストアを1つだけ行います(これは高価ですが、まれにしか起こらないので余裕があります)。それは... – Damon

+0

...他のスレッドが変更を受け取る前に瞬間が、それは通常問題ではありません。彼らは、結局、そうなるだろう。 *が問題*の場合は、ロック(おそらくリーダーライターロック)を使用する以外に選択肢はありません。その場合、パフォーマンスが悪くなります。あるいは、スレッドは、コンフィグステートのローカルコピーを保持する(または、再カウントされたコピーを共有する)ことができ、明確に定義された時刻に、タスクキューを介してコンフィグレーションを更新するように_explicitly_通知されます。それは可能なレースを排除します。 – Damon

答えて

2

Q1アトミックを読み込んだ後で、共有されている非アトミック変数を読み込もうとしないと訂正します。メモリバリアは

Q3は(コンパイルする場合)動作するはず

Q2は、私にはわからないアトミック操作間で起こる(ただし、下記参照)も非アトミック変数へのアクセスを同期します。しかし、

atomic<string> 

は、おそらく(lockfreeのshared_ptrを実装することは挑戦的で特許採掘場で)自由

Q4が動作しますが、再び、実装はおそらくlockfreeないロックではありません。

だから、おそらく読者-ライターがロック(デイモンがコメントで示唆するように)あなたの設定は、サイズのデータ​​が含まれている場合、1つの以上の機械語(ネイティブアトミックをCPUいるが、通常は動作します)単純で、より効果的かもしれ

[EDIT]しかし、

atomic<shared_ptr<TheWholeStructContainigAll> > 

はいくつかの意味を持っていることも、非ロックがフリーで:作家は全体の新しいコピーを作成する必要がありますけれども、このアプローチは、複数のコヒーレント値を必要とする読者のために、衝突確率を最小化します」それが何か変更されるたびに、

+0

'boost :: atomic 'はコンパイルされますが、動作しません。私はそれをロードしようとすると、デバッグアサーションを取得します。それは言う: "_BLOCK_TYPE_IS_VALID(pHead-> nBlockUse)"の "crt \ src \ dbgdel.cpp"(VC++ 2012)。ときどきアクセス違反が発生することがあります。私の 'std :: string'を' std :: atomic'でラップすると値をロードできますが、格納するとアクセス違反が発生します... –

2

質問の場合、答えは「依存しますが、おそらくはありません」です。実際には、単一の値が文字化けしていないことを気にしていれば、これは問題ありません。また、メモリの順序も気にしません。
通常、これは誤った前提です。

質問については

、、及びはい、これは動作しますが、それはおそらく(あなたが知らなくても、すべてのアクセスのために、内部的)なstringなどの複雑なオブジェクトのロックを使用します。おおよそ1つまたは2つのポインタのサイズであるむしろ小さなオブジェクトだけが、通常、ロックフリーの方法で原子的にアクセス/変更することができます。これはあなたのプラットフォームによっても異なります。

1つまたは2つの値を原子的に正常に更新するかどうかは大きな違いです。タスクが配列内で何らかの処理を行う場所の左と右の境界を区切る値leftrightがあるとします。それらがそれぞれ50と100であると仮定し、それらを原子ごとに101と150に変更します。したがって、もう1つのスレッドは、変更を50から101に集め、計算を開始し、101> 100とみなして終了し、その結果をファイルに書き込みます。その後、出力ファイルの名前をアトミックに変更します。
すべてがアトミック(したがって、通常より高価)でしたが、どれも有用ではありませんでした。結果は間違っていて、間違ったファイルに書き込まれました。
これは特定のケースでは問題ではない可能性がありますが、通常は(将来、要件が変わる可能性があります)。通常は本当にになります。完全セットのアトミックが必要です。

これは、多くの複雑な(または、複雑な)更新がある場合は、最初の場所でconfig全体に対して1つの大きな(リーダー/ライター)ロックを使用したいと思うかもしれませんそれは、20または30のロックを取得および解放すること、または50または100のアトミック操作を行うことよりも効率的であるからです。ただし、いずれの場合でもロックがパフォーマンスに重大な影響を与えることに注意してください。

上記のコメントで指摘したように、私は設定を変更するスレッドから構成の詳細コピーを作成し、コンシューマが通常のタスクとして使用する参照(共有ポインタ)の更新をスケジュールすることをお勧めします。そのコピー・モディファイ・パブリッシュ・アプローチは、MVCCデータベースの仕組みと少し似ています(これらも、ロッキングがパフォーマンスを犠牲にするという問題があります)。

コピーを変更すると、読者のみが共有状態にアクセスしていることがアサートされるため、読者または単一のライターに対して同期は必要ありません。読み書きは高速です。 コンフィギュレーションセットのスワッピングは、セットが完全で一貫した状態にあることが保証され、スレッドが何か他のことを行わないことが保証されている場合にのみ、明確に定義されたポイントで発生するため、あらゆる種類の醜い驚きは起こり得ません。ポインタよりもかなりのものについては

// consumer/worker thread(s) 
for(;;) 
{ 
    task = queue.pop(); 

    switch(task.code) 
    { 
     case EXIT: 
      return; 

     case SET_CONFIG: 
      my_conf = task.data; 
      break; 

     default: 
      task.func(task.data, &my_conf); // can read without sync 
    } 
} 


// thread that interacts with user (also producer) 
for(;;) 
{ 
    input = get_input(); 

    if(input.action == QUIT) 
    { 
     queue.push(task(EXIT, 0, 0)); 
     for(auto threads : thread) 
      thread.join(); 
     return 0; 
    } 
    else if(input.action == CHANGE_SETTINGS) 
    { 
     new_config = new config(config); // copy, readonly operation, no sync 
     // assume we have operator[] overloaded 
     new_config[...] = ...;   // I own this exclusively, no sync 

     task t(SET_CONFIG, 0, shared_ptr<...>(input.data)); 
     queue.push(t); 
    } 
    else if(input.action() == ADD_TASK) 
    { 
     task t(RUN, input.func, input.data); 
     queue.push(t); 
    } 
    ... 
} 
+0

残念ながら、 'boost :: atomic ' doesn '仕事。私はそれをロードしようとすると、デバッグアサーションを取得します。それは言う: "_BLOCK_TYPE_IS_VALID(pHead-> nBlockUse)"の "crt \ src \ dbgdel.cpp"(VC++ 2012)。ときどきアクセス違反が発生することがあります。 'std :: string'を' std :: atomic'でラップすると値をロードできますが、格納するとアクセス違反が発生します... –

2

、ミューテックスを使用します -

典型的なタスク駆動型アプリケーションは、多少、この(擬似コードのようなC++で)のようになります。 tbb(opensource)ライブラリは、複数の同時読み取りを可能にするリーダライタmutitsの概念をサポートしています(documentationを参照)。

関連する問題