2013-10-30 2 views
11

にしても、単純な2スレッドの通信たとえば、私は適切なメモリ順序を得るために、C11原子とmemory_fenceスタイルでこれを表現する難しさを持っている:C11メモリフェンスの使用量

共有データ:

volatile int flag, bucket; 

プロデューサスレッド:

while (true) { 
    int value = producer_work(); 
    while (atomic_load_explicit(&flag, memory_order_acquire)) 
     ; // busy wait 
    bucket = value; 
    atomic_store_explicit(&flag, 1, memory_order_release); 
} 

消費者THRE広告: - >旗-店 - >旗ロード - >負荷からバケット

while (true) { 
    while (!atomic_load_explicit(&flag, memory_order_acquire)) 
     ; // busy wait 
    int data = bucket; 
    atomic_thread_fence(/* memory_order ??? */); 
    atomic_store_explicit(&flag, 0, memory_order_release); 
    consumer_work(data); 
} 

私の知る限り理解し、上記のコードは、適切ストア・イン・バケットを注文します。しかし、私はバケットからのロードの間に競合状態が残っていて、バケットを新しいデータで再度書き直すと思う。バケット読み込みに続く命令を強制するには、バケット読み込みと次のatomic_storeの間に明示的にatomic_thread_fence()が必要であると思います。残念ながら、memory_order_seq_cstさえも、先行するロードで何かを強制するために、memory_order引数がないようです。

本当に汚い解決策は、民生用スレッドのbucketをダミー値で再割り当てすることです。これは、コンシューマの読み取り専用の概念と矛盾します。

古いC99/GCCの世界では、十分に強力であると私が信じている伝統的な__sync_synchronize()を使うことができました。

このいわゆるアンチ依存性を同期させる、より良いC11スタイルのソリューションは何でしょうか?

(もちろん、私は私が良く、このような低レベルのコーディングを避けるため、利用可能な高レベルの構文を使用する必要があることを承知していますが、私は理解したいと思います...)

+1

私はC++プログラマではありませんが、(概念的に) 'atomic_thread_fence()'呼び出しが必要であるかどうかはわかりません。フラグ更新は、リリースセマンティクスを有し、先行するストア命令がその全体にわたって再順序付けされるのを防止する(例えば、ストアを「データ」に)。 'data'へのストアは、' bucket'からの読み込みに依存しているので、読み込みがフラグ解放を過ぎても再順序付けできません。完全なフェンスが必要な場合、私はなぜそれを聞くのが大好きです。 –

+2

答えではないので、ちょうどコメント:C11の 'atomic_flag'データ型を再現したようです。これは正確にこの意味を実装していますが、最終的にはハードウェアでより直接的な実装をしています。 'atomic_flag'は、ロックフリーであることが保証されている唯一のアトミックデータ型です。したがって、より複雑な操作よりも常に優先されることになります。また、一貫性を確保するために特別なフェンスは必要ありません。 –

+0

Mike S、あなたの返信は私には魅力的だと思われますが...私はメモリフェンスがメモリサブシステム上のものを保証していて、ld/st opsに影響を与えていると思いました。上記の例では、 'data'はおそらくレジスタ変数になるため、その代入はストア演算を作成しません。それはメモリ同期のバケットからの負荷だけを残しますか? (それ以降のC11メモリの順序はありません) –

答えて

3

バケット読み込みの後に命令を強制するには、バケット読み込みと次のatomic_storeの間に明示的なatomic_thread_fence()が必要であると思います。

私はatomic_thread_fence()呼び出しが必要であるとは思わない:フラグ更新は、それ全体で並べ替えされてから任意の先行ロードまたはストア操作を防止し、リリースのセマンティクスを持っています。ハーブサッターによる正式な定義を参照してください:すべての読み取りとプログラム順序でそれに先行する同じスレッドでを書き込み後

書き込みのリリースでは、を実行します。

これにかかわらず、コンパイラはdataを格納することを選択した場合の、flag更新後に発生するように並べ替えされることbucketの読み出しを防止するべきです。別の答えについてコメントを私にもたらします

volatileは、LD/STの操作は、その後フェンスと一緒に注文することができ、生成されたがあることを保証します。ただし、データはローカル変数であり、volatileではありません。コンパイラは、ストア操作を避けるために、おそらくそれをレジスタに入れます。これにより、以降のフラグのリセットとともに、バケットからの負荷が発注されます。(それはおそらくどちらか、それを持っていて損はありませんが)それはそれを思わ

bucket読み取りがflag書き込みのリリースを超えて並べ替えることができない場合は問題ではないので、volatileは必要ありません。ほとんどの関数呼び出し(この場合はatomic_store_explicit(&flag))がコンパイル時のメモリバリアとして機能するため、これも不要です。コンパイラは、グローバル変数の読み込み順序をインラインされていない関数呼び出しよりも先に並べ替えることはありません。これは、その関数が同じ変数を変更する可能性があるためです。

@ MaximYegorushkinにも、互換性のあるアーキテクチャをターゲットにしているときにpause命令でビジー状態を改善することに同意します。 GCCとICCはどちらも_mm_pause(void)の組み込み関数を持っているようです(おそらく__asm__ ("pause;")に相当)。

+0

マイクの書き込みが正しいことをありがとう。したがって、cppreference.comでの 'memory_order_release'の今日の記述が間違っていることを確認します(先行する店舗にのみ有効です)。コンパイラが 'atomic_store_explicit'に関する知識を持ち、呼び出しをインライン化するならば、意図しないロードとスレッド間のデータ交換をスキップするレジスタに、不揮発性バケットをマッピングすることができますか? –

+0

それ自体が_incorrect_ではないかもしれません。技術的には、Sutterは「write-release」セマンティクスを記述していました。これは、「**セマンティクスのあるwrite **」、例えば 'atomic_store_explicit()'を意味します。これは単に 'atomic_thread_fence()'のような孤立したリリースの障壁によって提供される保証よりも強力であるかもしれません。この場合、 'memory_order_release'のドキュメンテーションは単純に_minimal_保証を記述しているかもしれません。残念なことに、このようなことに対する権限のある回答を得ることは非常に難しくなってきましたが、私が見た「リリースセマンティクス」の定義はすべてSutterの意見と一致しています。 –

+0

この回答を受け入れることは、cppreference.comが間違っていることを意味します。 @Mike、上記のpreshing.comへのリンクも同様です。私はちょうど見つかったhttp://stackoverflow.com/questions/16179938答えはまた、cppreference.comが間違っていると述べています:両方store.release前の負荷で注文する必要があります。しかし、2012年1月11日のC++のドラフトでは、「非公式に、Aのリリース操作を他のメモリ位置に作用させて他のスレッドから見えるようにする」と述べています。この「副作用」は現在読取りを除外していますか?これは、cppreference.com上で 'memory_order_release'を変更するケースを弱める:-( –

1

私は@MikeStrobelの中で言っていることに同意彼のコメント。

クリティカルセクションがリリースセマンティクスの獲得と終了で始まるため、atomic_thread_fence()は必要ありません。したがって、クリティカルセクション内の読み取りは、取得および書き込み前に並べ替えることはできません。このため、ここでもvolatileは不要です。

さらに、ここでは(pthread)スピンロックが使用されていない理由はありません。スピンロックはあなたのために同様の忙しいスピンを行いますが、それはまたpause instructionを使用しています。

一時停止固有の動的実行(特に、アウトオブオーダー実行)を実装するプロセッサとスピン待つループに使用されています。スピンウェイトループでは、ポーズ組み込み関数は、コードがロックの解放を検出する速度を向上させ、特に重要なパフォーマンスの向上をもたらします。 次の命令の実行は、実装固有の時間分遅れます。 PAUSE命令は、アーキテクチャ状態を変更しません。動的スケジューリングの場合、PAUSE命令はスピン・ループから抜けるペナルティを軽減します。

+0

'bucket'が' volatile'と宣言されていない場合、 'bucket'からのロードが常に正しい値を返すかどうかは疑問です:コンパイラが' bucket'のローカルレジスタコピーを作成するのを防ぐためにvolatileが必要です。 「揮発性」が依然としてフラグに必要なのかどうかはわかりませんが、原子組み込み関数はそれをカバーしている可能性があります。しかしながら、これらの関数のC11プロトタイプは 'volatile'ポインタ引数を指定しています。 –

+1

@ JosvEいいえ、揮発性は不要です。なぜなら、それにアクセスする前後にメモリの障壁があるからです。それは重要なことです。あなたの質問の詳細な扱いについては、http://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-1-of-2を参照してください。同様に揮発性である。 –

+1

OK、Suttersのプレゼンテーションを深く知りました...コンパイラが原子フェンスを使用して順序付けをする必要があります。それ以上の「volatile」宣言は必要ありません。ありがとう。 –

-1

直接の答え:

お店がmemory_order_release操作であることは、あなたのコンパイラがフラグの店の前にストア命令のためのメモリフェンスを放出しなければならないことを意味します。これは、解放されたデータが解釈を開始する前に、他のプロセッサが解放されたデータの最終状態を確認するために必要です。したがって、いいえ、2番目のフェンスを追加する必要はありません。


長い答え:

上述のように、何が起こるかはコンパイラがフェンスの組み合わせの中にあなたのatomic_...指示を変換し、メモリアクセスということです。基本的な抽象化は原子負荷ではなく、メモリフェンスです。それは新しいC++抽象概念があなたに違った考えを誘惑するにもかかわらず、どのように動作するかです。そして、私は個人的にメモリフェンスがC++の壊れた抽象概念よりもはるかに考えるのが簡単だと感じています。

ハードウェアの観点からは、ロードとストアの相対的な注文がです。 e。フラグがプロデューサに書き込まれる前にバケットへの書き込みが完了し、フラグのロードがコンシューマ内のバケットのロードより古い値を読み取ることを示します。あなたが実際に必要なことはこれである、と述べた

:我々はピンポンを再生する二つのプロセスを持っているので、ラベル「プロデューサー」と「消費者」は、ここではそれぞれになってプロデューサーを誤解していることを

//producer 
while(true) { 
    int value = producer_work(); 
    while (flag) ; // busy wait 
    atomic_thread_fence(memory_order_acquire); //ensure that value is not assigned to bucket before the flag is lowered 
    bucket = value; 
    atomic_thread_fence(memory_order_release); //ensure bucket is written before flag is 
    flag = true; 
} 

//consumer 
while(true) { 
    while(!flag) ; // busy wait 
    atomic_thread_fence(memory_order_acquire); //ensure the value read from bucket is not older than the last value read from flag 
    int data = bucket; 
    atomic_thread_fence(memory_order_release); //ensure data is loaded from bucket before the flag is lowered again 
    flag = false; 
    consumer_work(data); 
} 

注意し、消費者は順番にそれは他の

atomic_thread_fence() ...に有用な値を書き込むための「穴」を生成しながら、一つのスレッドが有益な値を生成するだけのことだあなたが必要とするすべてであり、それが直接atomic_...抽象以下のアセンブラ命令に変換するので、それはあります最速のアプローチであることが保証されています。

+0

'flag'の連動した(原子的な)読み取りがなければ、プロデューサとコンシューマが別々のCPUで動作している場合、最新の' flag'値が読み込まれることは期待しません。更新が注文されるかもしれませんが、新しい結果は_seen_ではないかもしれません。同様の理由で、彼はまた、フラグを更新するときに連動したストアが必要になると思います。最低でも、リリースフェンスは 'フラグ'の割り当てに従うべきです。 –

+0

@MikeStrobelいいえ、リリースはフラグアサインメントの前になければなりません。ストアフェンスです。前のストアは、ストアの前に有効になっている必要があります。アトミシティについて:はい、フラグへの代入はアトミックでなければなりませんが、適切に整列された 'int'へのストアがアトミックではないアーキテクチャを表示します。そのための言語サポートは必要ありません。 – cmaster

+0

おっと、はい、店の後にリリースフェンスを動かすことについて私が言ったことは無視してください。しかし、アトミックに関しては、私の指摘は、 'flag'への読み書きはアトミックではなく、インターロックされていないということではない(おそらくこのAPIのアトミックな読み書き機能です)。あなたのバージョンでは、他のCPUがローカルキャッシュから 'flag'の古いバージョンを読み込もうとしないことをどのように保証しますか? –