マルチコアx86マシンでは、core1で実行されているスレッドが整数変数a
をインクリメントすると同時に、コア2のスレッドもそれをインクリメントするとします。 a
の初期値が0だったとすると、最終的には常に2
になりますか?それとも他の価値があるのでしょうか? a
がvolatile
として宣言されており、アトミック変数(C++のatomic <>やgccの組み込みアトミック操作など)を使用していないと仮定します。
このような場合、a
の値が実際に常に2である場合、x86-64のlong int
も同じプロパティを持つことを意味します。つまり、a
は最終的には常に2になりますか?
X86のインクリメントメモリマシン命令は、[〜#〜] lock [〜#〜]プレフィックスを付けて使用する場合にのみアトミックです。
x ++ CおよびC++ではアトミックな動作はありません。プロセッサがXを読み書きしている競合のために、ロック解除されたインクリメントを行う場合、2つの別々のプロセッサがインクリメントを試みると、1つのインクリメントのみ、または両方が表示される可能性があります(2番目のプロセッサが初期値を読み取った可能性があります。それ、そして最初の結果を書き戻した後に書き戻しました)。
C++ 11はアトミックインクリメントを提供し、ほとんどのベンダーコンパイラには、特定の組み込み整数型(通常はintおよびlong)のアトミックインクリメントを発生させる慣用的な方法があると思います。コンパイラのリファレンスマニュアルを参照してください。
「大きな値」(たとえば、多倍長整数)をインクリメントする場合は、セマフォなどの標準のロックメカニズムを使用してインクリメントする必要があります。
アトミック読み取りについても心配する必要があることに注意してください。 x86では、32ビットまたは64ビットの値の読み取りは、64ビットのワードアラインされている場合はアトミックです。それは「大きな価値」には当てはまりません。ここでも、標準のロックが必要です。
これが特定の実装(gcc)でアトミックではないという1つの証拠です。ご覧のとおり(?)、gccは次のようなコードを生成します。
それはアトミックではありません。
$ 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
の結果のメモリアドレスを入力します。ファイルがリンクされています。
誰もあなたの実際の質問に答えておらず、代わりに常に機能する方法でそれを行う方法を示しているので:
スレッド1は値0をロードします
スレッド2は値0をロードします
スレッド1はストア1をインクリメントします
スレッド2は、値のローカルレジスタコピーをインクリメントし、1を格納します。
ご覧のとおり、最終結果は2ではなく1に等しい値です。これは、最後に常に2になるとは限りません。
保証されていません。 lock xadd
命令を使用して同じ効果を達成するか、C++ std::atomic
を使用するか、#pragma omp atomic
を使用するか、または他の任意の数の同時実行ソリューションを使用して、車輪の再発明。