2016-04-08 7 views
13

フラグが設定されるまで実行されるスレッドを実行しています。std :: atomicは揮発性であるべきですか?

std::atomic<bool> stop(false); 

void f() { 
    while(!stop.load(std::memory_order_{relaxed,acquire})) { 
    do_the_job(); 
    } 
} 

コンパイラがこのようなループを展開できるかどうかは疑問です(私は起こりたくありません)。

void f() { 
    while(!stop.load(std::memory_order_{relaxed,acquire})) { 
    do_the_job(); 
    do_the_job(); 
    do_the_job(); 
    do_the_job(); 
    ... // unroll as many as the compiler wants 
    } 
} 

ボラティリティとアトミック性は直交していると言われていますが、少し混乱しています。コンパイラはアトミック変数の値をキャッシュしてループを展開することはできますか?コンパイラがループを展開することができる場合、私はvolatileをフラグに入れなければならないと確信しています。

volatileを入力してください。


あいまって申し訳ありません。私は(私が)並べ替えが何であるかを理解しており、何がmemory_order_*を意味するのかを理解しており、私は完全に理解していると確信していますvolatileです。

ループはifのように無限にループすると思います。

void f() { 
    if(stop.load(std::memory_order_{relaxed,acquire})) return; 
    do_the_job(); 
    if(stop.load(std::memory_order_{relaxed,acquire})) return; 
    do_the_job(); 
    if(stop.load(std::memory_order_{relaxed,acquire})) return; 
    do_the_job(); 
    ... 
} 

与えられたメモリの注文がアトミック負荷を通過して移動されることから、配列決定、前の操作を防ぐことはできませんので、私はそれが揮発せずにいた場合、それは再配置することができると思います。

void f() { 
    if(stop.load(std::memory_order_{relaxed,acquire})) return; 
    if(stop.load(std::memory_order_{relaxed,acquire})) return; 
    if(stop.load(std::memory_order_{relaxed,acquire})) return; 
    ... 
    do_the_job(); 
    do_the_job(); 
    do_the_job(); 
    ... 
} 

アトミックにvolatileが含まれていない場合、コードは最悪の場合でもこのように変換される可能性があります。

このような非常識な実装は決してありませんが、それでも状況は可能です。私はこれを防ぐための唯一の方法は、volatileを原子変数に置き、それについて尋ねることです。

私が作った推測はたくさんありますが、間違っているかどうか教えてください。

+0

私はそうは思わない。最近、 'std :: atomic'のためにたくさんのことを見ましたが、誰もそれはすべきではないと言いました。私は、クラス内にはどこかに「揮発性の」変数があると思う。 – Nick

+2

[同時実行性:C++ 11メモリモデルでのアトミックおよび揮発性]の可能な複製(http://stackoverflow.com/questions/8819095/concurrency-atomic-and-volatile-in-c11-memory-model) –

+1

いいえ、それは揮発性ではありません。 –

答えて

7

コンパイラは、アトミック変数の値をキャッシュしてループを展開できますか?

コンパイラは、アトミック変数の値をキャッシュできません。

しかし、std::memory_order_relaxedを使用しているため、コンパイラは、他のロードおよびストアに関して、このアトミック変数からロードおよびストアを自由に並べ替えることができます。

また、この変換単位で定義が使用できない関数の呼び出しは、コンパイラのメモリバリアです。これは、呼び出しを周囲のロードとストアに関して並べ替えることができないこと、そしてすべての非ローカル変数が、呼び出し後にメモリから再ロードされなければならないことを意味します。 (アドレスが他の場所に渡されなかったローカル変数は、リロードされません)。それは、C++メモリモデルに違反するので、あなたが避けたいコードの

変換は有効な変換ではありません。最初のケースであなたはdo_the_jobへの呼び出しに続くアトミック変数の1つの負荷を持っている、で2番目に、複数の通話があります。変換されたコードの観察された挙動は異なるかもしれません。


そしてstd::memory_orderからノート:

実行のスレッド内で揮発性

との関係、(読み取りおよび書き込み)アクセスするすべての揮発性のオブジェクトを並べ替えることがないことが保証されているに揮発性アクセスはスレッド間同期を確立しないため、この順序は別のスレッドによって保証されているとは限りません。また

、揮発性アクセスはアトミックではない(同時読み取りと書き込みがデータ競合である)および(不揮発性メモリアクセスが自由揮発アクセス周りに並べ替えることができる)メモリを注文していません。

このビット不揮発性メモリは、自由に揮発性アクセス周りに並べ替えることができるアクセス緩和負荷および記憶は、他のロードとストアに関して並べ替えることができるので、同様にリラックスアトミックについても同様です。

つまり、アトミックをvolatileに変更しても、コードの動作は変更されません。


にかかわらず、C++ 11個のアトミック変数は、volatileキーワードでマークする必要はありません。


次に、g ++ - 5.2がどのように原子変数を使用するかの例を示します。以下の機能:

__attribute__((noinline)) int f(std::atomic<int>& a) { 
    return a.load(std::memory_order_relaxed); 
} 

__attribute__((noinline)) int g(std::atomic<int>& a) { 
    static_cast<void>(a.load(std::memory_order_relaxed)); 
    static_cast<void>(a.load(std::memory_order_relaxed)); 
    static_cast<void>(a.load(std::memory_order_relaxed)); 
    return a.load(std::memory_order_relaxed); 
} 

__attribute__((noinline)) int h(std::atomic<int>& a) { 
    while(a.load(std::memory_order_relaxed)) 
     ; 
    return 0; 
} 

g++ -o- -Wall -Wextra -S -march=native -O3 -pthread -std=gnu++11 test.cc | c++filt > test.Sでコンパイルには、次のアセンブリを生成:

f(std::atomic<int>&): 
    movl (%rdi), %eax 
    ret 

g(std::atomic<int>&): 
    movl (%rdi), %eax 
    movl (%rdi), %eax 
    movl (%rdi), %eax 
    movl (%rdi), %eax 
    ret 

h(std::atomic<int>&): 
.L4: 
    movl (%rdi), %eax 
    testl %eax, %eax 
    jne .L4 
    ret 
+0

私はLTOと呼ばれる獣があるので、関数がコンパイラの壁になるとは思えません。同じ変数に対する2つの連続した原子ロード操作を1つのロードに変換できないことを意味しますか? – kukyakya

+0

@kukyakyaメモリモデルは、アトミック変数が別のスレッドによって変更される可能性があることを示しているため、ロードを省略できません。アトミック変数のロードを拒否すると、アトミック変数へのストアが他のスレッドには見えなくなり、アトム変数へのストアの可視性を保証するメモリモデルに違反します。 –

+0

"アトミック変数へのストアの可視性を保証します。ストアが一定の期間内に他のロードから見えるようになる保証はありません。私たちが持っている最高のものは、「実装は、合理的な時間内にアトミック・ストアをアトミック・ロードに見えるようにする必要があります」という要件ではなく、規範的な励まし(「はず」)です。 –

2

do_the_job()stopを変更しない場合、コンパイラはループを展開、またはことができない場合、それは問題ではありません。

std::memory_order_relaxedは、各操作がアトミックであることを確認していますが、並べ替えアクセスが妨げられることはありません。つまり、別のスレッドがstoptrueと設定すると、アクセスが再調整される可能性があるため、ループが数回実行される可能性があります。したがって、アンロールされたループと同じ状況です。別のスレッドがstoptrueに設定した後に、do_the_job()が数回実行される可能性があります。

volatileを使用しないでください。std::memory_order_acquirestd::memory_order_releaseを使用してください。

+0

私はあなたの意見を得ました。ロード操作が最後の値を取得することは保証されていないので、関数への呼び出し回数を制限することは意味がありません。追加したケースはどうですか? – kukyakya

+0

'do_the_job()'が何をしているのか、 'stop'を設定しているスレッドが何をしているのか分からなければ、このことを考えるのは難しいです。彼らが共通のデータにアクセスするならば、2人の間に確実により多くの同期化が必要であると私は思う。より詳細な例を投稿できますか? – alain

関連する問題