14

今日私はこの質問に出くわしました:

あなたはコードを持っています

static int counter = 0;
void worker() {
    for (int i = 1; i <= 10; i++)
        counter++;
}

worker2つの異なるスレッドから呼び出された場合counter、両方が終了した後、どのような値になりますか?

私は実際にそれが何でもあり得ることを知っています。しかし、私の内部の内臓は、それcounter++はおそらく単一のアセンブラー命令に変換され、両方のスレッドが同じコアで実行さcounterれる場合、20になると言っています。

しかし、これらのスレッドが異なるコアまたはプロセッサで実行されている場合、マイクロコードに競合状態が発生する可能性はありますか?1つのアセンブラ命令は常にアトミック操作と見なすことができますか?

4

11 に答える 11

20

特にx86の場合、 example: に関してcounter++は、コンパイルできる方法がいくつかあります。最も簡単な例は次のとおりです。

inc counter

これは、次のマイクロ オペレーションに変換されます。

  • counterCPU の隠しレジスタにロードする
  • レジスタをインクリメントする
  • 更新されたレジスタを格納しますcounter

これは基本的に次のものと同じです。

mov eax, counter
inc eax
mov counter, eax

ロードとストアの間に他のエージェントが更新した場合counter、ストアの後には反映されないことに注意してくださいcounter。このエージェントは、同じコアの別のスレッド、同じ CPU の別のコア、同じシステムの別の CPU、または DMA (ダイレクト メモリ アクセス) を使用する外部エージェントである可能性があります。

incこれがアトミックであることを保証したい場合は、lockプレフィックスを使用します。

lock inc counter

lockcounterロードとストアの間で誰も更新できないことを保証します。


lockより複雑な命令については、プレフィックスをサポートしない限り、通常、アトミックに実行されるとは想定できません。

于 2009-07-07T10:06:10.540 に答える
8

常にではありません-一部のアーキテクチャでは、1つのアセンブリ命令が1つのマシンコード命令に変換されますが、他のアーキテクチャでは変換されません。

さらに、使用しているプログラム言語が、一見単純なコード行を1つのアセンブリ命令にコンパイルしているとは決して想定できません。さらに、一部のアーキテクチャでは、1つのマシンコードがアトミックに実行されると想定することはできません。

代わりに、コーディングしている言語に応じて、適切な同期手法を使用してください。

于 2009-07-07T08:54:55.520 に答える
8

答えは次のとおりです。

アセンブラ命令とは何かという混乱があります。通常、1 つのアセンブラー命令は正確に 1 つの機械語命令に変換されます。マクロを使用する場合は例外ですが、その点に注意してください。

とはいえ、質問は要約すると、1 つのマシン命令がアトミックかということです。

古き良き時代、そうでした。しかし今日では、複雑な CPU、長時間実行される命令、ハイパースレッディングなどにより、そうではありません。一部の CPU は、一部のインクリメント/デクリメント命令がアトミックであることを保証します。その理由は、非常に単純な同期に適しているからです。

また、一部の CPU コマンドはそれほど問題になりません。単純なフェッチ (プロセッサが 1 つの部分でフェッチできる 1 つのデータ) がある場合、分割するものがまったくないため、フェッチ自体はもちろんアトミックです。しかし、整列されていないデータがあると、再び複雑になります。

答えは次のとおりです。ベンダーの機械取扱説明書をよく読んでください。疑いなく、そうではありません!

編集:ああ、私は今それを見ました、あなたは++counterも求めています. 「翻訳される可能性が最も高い」という記述はまったく信用できません。もちろん、これはコンパイラにも大きく依存します。コンパイラがさまざまな最適化を行っている場合は、さらに困難になります。

于 2009-07-07T09:12:03.737 に答える
6
  1. ハイパースレッディングテクノロジを使用しない単一の32ビットプロセッサでの32ビット以下の整数変数のインクリメント/デクリメント操作はアトミックです。
  2. ハイパースレッディングテクノロジを搭載したプロセッサまたはマルチプロセッサシステムでは、インクリメント/デクリメント操作がアトミックに実行されるとは限りません。
于 2009-07-07T08:55:15.503 に答える
4

Nathanのコメントによって無効化: Intel x86アセンブラを正しく覚えている場合、INC命令はレジスタに対してのみ機能し、メモリ位置に対しては直接機能しません。

したがって、counter ++はアセンブラの単一の命令ではありません(ポストインクリメント部分を無視するだけです)。少なくとも3つの命令になります。レジスタにカウンタ変数をロードし、レジスタをインクリメントし、カウンタにレジスタをロードします。そして、それはx86アーキテクチャーのためだけです。

つまり、言語仕様で指定されており、使用しているコンパイラが仕様をサポートしている場合を除いて、アトミックであることに依存しないでください。

于 2009-07-07T09:08:18.237 に答える
3

いいえ、これを想定することはできません。コンパイラ仕様に明記されていない限り。さらに、1つのアセンブラ命令が実際にアトミックであることを保証することはできません。実際には、各アセンブラ命令はマイクロコード操作の数(uops)に変換されます。
また、競合状態の問題は、メモリモデル(コヒーレンス、シーケンシャル、リリースコヒーレンスなど)と密接に関連しており、それぞれの答えと結果が異なる可能性があります。

于 2009-07-07T08:54:20.823 に答える
3

もう1つの問題は、変数をvolatileとして宣言しない場合、生成されたコードはループの反復ごとにメモリを更新せず、ループの最後でのみメモリが更新されることです。

于 2009-07-07T08:58:19.350 に答える
1

あなたの質問に対する実際の答えではないかもしれませんが、(これが C# または別の .NET 言語であると仮定して)counter++本当にマルチスレッドのアトミックになりたい場合は、System.Threading.Interlocked.Increment(counter).

counter++なぜ/どのようにアトミックにできないかについての実際の情報については、他の回答を参照してください。;-)

于 2009-07-07T09:47:03.480 に答える
-1

他の多くのプロセッサでは、メモリ システムとプロセッサの間の分離が大きくなっています。(多くの場合、これらのプロセッサは、ARM や PowerPC などのメモリ システムに応じてリトル エンディアンまたはビッグ エンディアンになります)、メモリ システムが読み取りと書き込みの順序を変更できる場合、これはアトミックな動作にも影響します。

この目的のために、メモリ バリアがあります ( http://en.wikipedia.org/wiki/Memory_barrier ) 。

つまり、Intel では (関連するロック プレフィックスを使用して) アトミックな命令で十分ですが、メモリ I/O が同じ順序ではない可能性があるため、非 Intel ではさらに実行する必要があります。

これは、「ロックフリー」ソリューションを Intel から他のアーキテクチャに移植する際の既知の問題です。

(x86 上のマルチプロセッサ (マルチコアではない) システムも、少なくとも 64 ビット モードではメモリ バリアが必要なようです。

于 2009-07-09T10:38:22.193 に答える
-3

アクセス時に競合状態になると思います。

カウンターをインクリメントする際のアトミック操作を確実にしたい場合は、++counterを使用する必要があります。

于 2009-07-07T08:53:55.270 に答える