web-dev-qa-db-ja.com

インクリメントはx86の整数アトミックですか?

マルチコアx86マシンでは、core1で実行されているスレッドが整数変数aをインクリメントすると同時に、コア2のスレッドもそれをインクリメントするとします。 aの初期値が0だったとすると、最終的には常に2になりますか?それとも他の価値があるのでしょうか? avolatileとして宣言されており、アトミック変数(C++のatomic <>やgccの組み込みアトミック操作など)を使用していないと仮定します。

このような場合、aの値が実際に常に2である場合、x86-64のlong intも同じプロパティを持つことを意味します。つまり、aは最終的には常に2になりますか?

17
pythonic

X86のインクリメントメモリマシン命令は、[〜#〜] lock [〜#〜]プレフィックスを付けて使用する場合にのみアトミックです。

x ++ CおよびC++ではアトミックな動作はありません。プロセッサがXを読み書きしている競合のために、ロック解除されたインクリメントを行う場合、2つの別々のプロセッサがインクリメントを試みると、1つのインクリメントのみ、または両方が表示される可能性があります(2番目のプロセッサが初期値を読み取った可能性があります。それ、そして最初の結果を書き戻した後に書き戻しました)。

C++ 11はアトミックインクリメントを提供し、ほとんどのベンダーコンパイラには、特定の組み込み整数型(通常はintおよびlong)のアトミックインクリメントを発生させる慣用的な方法があると思います。コンパイラのリファレンスマニュアルを参照してください。

「大きな値」(たとえば、多倍長整数)をインクリメントする場合は、セマフォなどの標準のロックメカニズムを使用してインクリメントする必要があります。

アトミック読み取りについても心配する必要があることに注意してください。 x86では、32ビットまたは64ビットの値の読み取りは、64ビットのワードアラインされている場合はアトミックです。それは「大きな価値」には当てはまりません。ここでも、標準のロックが必要です。

28
Ira Baxter

これが特定の実装(gcc)でアトミックではないという1つの証拠です。ご覧のとおり(?)、gccは次のようなコードを生成します。

  1. 値をメモリからレジスタにロードします
  2. レジスタの内容をインクリメントします
  3. レジスタをメモリに保存します。

それはアトミックではありません。

$ cat t.c
volatile int a;

void func(void)
{
    a++;
}
[19:51:52 0 ~] $ gcc -O2 -c t.c
[19:51:55 0 ~] $ objdump -d t.o

t.o:     file format elf32-i386


Disassembly of section .text:

00000000 <func>:
   0:   a1 00 00 00 00          mov    0x0,%eax
   5:   83 c0 01                add    $0x1,%eax
   8:   a3 00 00 00 00          mov    %eax,0x0
   d:   c3                      ret

mov命令の0x0にだまされないでください。そこには、4バイトのスペースがあり、このオブジェクトの場合、リンカーはaの結果のメモリアドレスを入力します。ファイルがリンクされています。

8
nos

誰もあなたの実際の質問に答えておらず、代わりに常に機能する方法でそれを行う方法を示しているので:

スレッド1は値0をロードします

スレッド2は値0をロードします

スレッド1はストア1をインクリメントします

スレッド2は、値のローカルレジスタコピーをインクリメントし、1を格納します。

ご覧のとおり、最終結果は2ではなく1に等しい値です。これは、最後に常に2になるとは限りません。

8
Michael Dorgan

保証されていません。 lock xadd命令を使用して同じ効果を達成するか、C++ std::atomicを使用するか、#pragma omp atomicを使用するか、または他の任意の数の同時実行ソリューションを使用して、車輪の再発明。

5
Jon Purdy