2016-06-01 18 views
0

友人同士のベット。 sum変数はグローバルとして定義されています。 となり、ループ1..100で実行される2つのスレッドがあり、ループごとに合計が1ずつ増加します。演算子++(接頭辞)スレッド付き

何が印刷されますか? "sum ="?

int sum = 0; 

void func(){ 
    for (int i=0 ; i<= 100; i++){ 
     sum++; 
    } 
} 

int main(){ 

    t1 = Thread(func); 
    t2 = Thread(func); 

    t1.start(); 
    t2.start(); 

    t1.join(); 
    t2.join(); 

    cout << "sum = " << sum; 

    return 0; 

} 
+0

何かが起こる可能性があります。好奇心の疎かに、私はこのプログラムを修正しました。これは、UB - http://stackoverflow.com/questions/37325524/does-integer-overflow-cause-undefined-behavior-because-of-memory-corruption/37325854#37325854 –

+1

です。 C++ 11と私は最初の結果として "202"を持っていました。これは、範囲を考える誰もが100-200が間違っていることを意味します。考えられる理由としては、コードをよく見てください;)しかし、UBのままです。 – stefaanv

+0

タイトルは** prefix ** operator ++を参照していますが、コードは** postfix **を使用しています。結果に影響するものではない... –

答えて

6

合計の単一の値はありません。競合条件が0の場合、値は200になります。ループの繰り返しごとに競合条件がある場合(おそらく)、競合条件は100以下になる可能性があります。

おそらくsum ++は原子操作と考えるかもしれませんが、sum = sum + 1の構文上の砂糖です。この操作では競合状態が発生する可能性があるため、実行するたびにsumが異なることがあります。

sumの現在の値が10であるとします。次に、t1がループに入り、sum(10)の値を読み込み、t2が実行を開始するように停止します。 t2はt1と同じ値(10)の和を読み取ります。次に、各スレッドがインクリメントすると、両方とも11に増加します。他の競合条件がない場合、合計の終了値は199になります。

さらに悪いケースです。 sumの現在の値が再び10であるとします。 t1はループに入り、sum(10)の値を読み込み、t2が実行を開始するように停止します。 t2は再びsum(10)の値を読み取り、それ自体が停止します。今度はt1が再び実行を開始し、合計値を20に設定して10回ループします。次に、t2が再び起動し、合計を11に増やすので、実際にsumの値を減らしています。

+2

値がどのように減少するかを示す良い例です。 – NathanOliver

+1

基礎となるハードウェアの書き込みがアトミックでないか、変数が整列していない場合はさらに悪化します。値の範囲が限られているため、あるスレッドが0x0100、2番目の0x00FFを書き込もうとしたときに、最初のスレッドから最下位ビットで終了し、2番目から最上位ビットで終了すると想像してください。 –

+0

デクリメントされません。あなたはちょうど10の増分を失った。 – AhmadWabbi

8

それはあなたが共有変数にアクセスする複数のスレッドを持っており、少なくともそれらの上のあなたが同期を必要とし、その後の作家であるとき、私は42を言うためにゴングていますので、未定義の動作です。その同期がない場合は、未定義の動作があり、何が起こるかはわかりません。

std::mutexを使用するか、std::atomicを使用して同期を取得し、プログラムの動作を定義することができます。

+0

私は重要なコードをロックするためにmutexが必要であることを理解しています。値の範囲はどのようになりますか? –

+2

@ItayAveksisそれは何でもかまいませんが、100-200の間の何かが可能性があります。問題は、インクリメントセクションで読み込みが途切れる可能性があるため、実際の値が増えているかどうかわからないことです。 – NathanOliver

+0

でもまだ100です。= sum <= 200 –

1

増分がatomicではないため、undefined behaviourになります。

+0

ok。値の範囲はどのようになりますか? –

+0

あなたはフランス語のcppreferenceにリンクしました。私はフランス語のcppreferenceがあることにも気づいていませんでした – stefaanv

+1

@ItayAveksis:第1に「未定義の振る舞い」は、それ以上の仮定をしてはならないことを意味し、第2に、 'Thread'がどのように実装されているかわかりません。 – stefaanv

0

これは100〜200の間のランダム値になります。相互排除なしで2つのスレッド間に競合状態が存在します。したがって、いくつかの++操作は失われます。これは、スレッドのすべての++演算が失われたときに100を得て、何も失われないときに200を得る理由です。間に何かが起こるかもしれません。

+0

いいえ。未定義の動作は**未定義**を意味します。結果は100と200の間に制限されません(このプログラムのシリアルバージョンは** 202 **を生成します)。 –

+0

100と202の間にあります(ループではi <= 100なので200ではありません)。 「未定義」は値を知ることができないことを意味しますが、範囲を知ることができます。コードには202のインクリメント演算があります。少なくとも100人が実行されます。せいぜい、それらのすべてが実行されます。たとえば、99という値を返す実行シナリオを教えてもらえますか? @PeteBecker – AhmadWabbi

+0

言語定義によれば、このプログラムの動作は**未定義です**。つまり、言語の定義がプログラムの内容を教えてくれないということです。 (結果が特定の範囲内にあることが要求された場合、結果は** unspecified **となります)**あなたは何が**起こるかもしれないか推測することができますが、コンパイラがその処理方法を文書化しない限り、何もない。コンパイラが複数のスレッドが同期なしで同じ場所に書き込んでいることを観察して、インクリメントを最適化し、値を42に設定するのを妨げるものは何もありません。 –

関連する問題