2016-07-01 9 views
-1
int main() { 
    int f = 0, x=0; 

    std::thread *t = new std::thread([&f, &x](){ while(f == 0); std::cout << x << endl;}); 
    std::thread *t2 = new std::thread([&f, &x](){ x = 42; f = 1;}); 

    t->join(); 
    t2->join(); 
    return 0; 

} 

、theoritically stdoutが(私たちは、結果として42を期待している私たちの直感に反対0に等しい取得することが可能である。しかし、CPUは発注指示の外に実行することができ、実際に、それはにpossbileですそのために、プログラムを実行します。 マルチスレッドとOOO実行。私が知っていることから、

ので、thread#2(なぜならOOOのmeachanismの)最初の実行される第2のコア上f = 1、その後、thread#1(私たちは私たちのCPUで> 1つのコアがあると)最初のコアで実行される最初のプログラム:while(f == 0); std::cout << x << endl。したがって、出力はです。

私はそのような出力を得ようとしましたが、私はいつも42を取得します。私はそのプログラムを1000000回実行し、結果は常に同じ= 42でした。

(私はそれが安全ではないことを知っています、データ競争があります)。

私の質問は以下のとおりです。

  1. アム私は正しいか、私が間違っていますか?どうして?
  2. 私が正しいとすれば、出力を0にすることは強制できますか?
  3. このコードを安全にするには?私はmutex/semaphoresについて知っていて、mutexでfを保護することができましたが、私はメモリフェンスについて何か聞いてきました。
+3

UBはUBです。 std :: atomicを参照してください。 –

+2

アトミックの使用は実際にはここで重要です。あなたのコードは私のシステム(gcc6.1 -O3)で終わることさえありません。また、なぜポインタビジネスですか?スレッドを自動変数として宣言するだけです。 –

+0

@RichardCritten、アトミックな操作が順不同で実行できないことは確かですか? – Gilgamesz

答えて

4

しかし、CPUは発注指示の外に実行することができ、実際に、その順序でプログラムを実行するpossbileです:

実行時に負荷の並べ替えと異なるアウト・オブ・オーダー/店舗はグローバルに見えるようになります。 OoOEは、順番に実行されているプログラミングの錯覚を保持します。 OoOEなしでメモリの並べ替えが可能です。イン・オーダー・パイプライン・コアであっても、そのストアをバッファリングする必要があります。 parts of this answer, for exampleを参照してください。

私が正しいとすれば、出力が0になるように強制できますか?

Storestoreの並べ替えではなく、which only does StoreLoad reorderingではありません。 compiler reorders the stores to x and f at compile timeの場合は、f==1と表示された後にx==0と表示されることがあります。さもなければあなたはそれを決して見ません。

スレッド1を起動してからスレッド2を起動すると、短いスリープにより、スレッド1が修正する前にxに回転していることが確認されます。次に、thread2は必要なく、メインスレッドから実際にストアを実行できます。

Nehalemの6k回の反復で、x86での実行時メモリの並べ替えを観察する実際のプログラムでは、Jeff PreshingのMemory Reordering Caught In The Actを見てください。

弱い順序のアーキテクチャでは、テストプログラムのようなもので実行時にStoreStoreの並べ替えが行われることがあります。しかし、変数が異なるキャッシュラインにあるように手配しなければならない可能性があります。また、1回のプログラム呼び出しではなく、ループでテストする必要があります。このコードを安全にする方法


?私はミューテックス/セマフォについて知っていて、ミューテックスでfを保護することができましたが、私はメモリフェンスについて何か聞いてきました。

fにあなたのアクセスにacquire/release semanticsを取得するにはC++11 std::atomicを使用してください。

std::atomic<uin32t_t> f; // flag to indicate when x is ready 
uint32_t x; 

... 

// don't use new when a local with automatic storage works fine 
std::thread t1 = std::thread([&f, &x](){ 
    while(f.load(std::memory_order_acquire) == 0); 
    std::cout << x << endl;}); 

// or sleep a few ms, and do t2's work in the main thread 
std::thread t2 = std::thread([&f, &x](){ 
    x = 42; f.store(1, std::memory_order_release);}); 

f = 1のようなもののデフォルトのメモリ順序は、x86上MFENCEを必要mo_seq_cst、または他のアーキテクチャ上の同等の高価な障壁です。

x86では、弱いメモリ順序はコンパイル時の並べ替えを妨げるだけですが、バリア命令は必要ありません。

std :: atomicはまた、@ Baumのコメントのように、スレッド1のwhileループのうち、fの負荷をコンパイラーが持ち上げるのを防ぎます。 (アトミックにはvolatileのようなセマンティクスがあるため、格納された値が非同期的に変更されることが想定されています)データ競合は未定義の動作なので、コンパイラは通常、エイリアス解析がループ内のポインタ値を変更しないでください)。

+0

Peter Cordes、ありがとう!私はエイリアス分析についてまだ何かを読まなければならない。 – Gilgamesz

関連する問題