Cでは、i+=1;
アトミックですか?
12 に答える
C 標準では、アトミックかどうかは定義されていません。
実際には、特定の操作がアトミックである場合に失敗するコードを書くことは決してありませんが、そうでない場合に失敗するコードを書くことは十分に可能です。だから、そうではないと仮定してください。
いいえ。
sig_atomic_t
C 言語標準によってアトミックであることが保証されている唯一の操作は、 で定義されているタイプ の変数への値の代入または値の取得<signal.h>
です。
(C99、章 7.14 シグナル処理。)
C で定義されています。実際には、そうかもしれません。アセンブラで書きます。
規格は保証しません。
したがって、移植可能なプログラムは仮定を行いません。「アトミックである必要がある」という意味なのか、それとも「私の C コードではアトミックであることが起こる」という意味なのかは明確ではありません。
すべてのマシンにインクリメント メモリ op があるわけではありません。値を操作するために値をロードして保存する必要があるため、答えは「決して」ありません。
インクリメント メモリ op を持つマシンでは、コンパイラがロード、インクリメント、ストア シーケンスを出力しない、または他の非アトミック命令を使用しないという保証はありません。
インクリメント メモリ操作を行うマシンでは、他の CPU ユニットに対してアトミックである場合とそうでない場合があります。
アトミック インクリメント メモリ op を持つマシンでは、アーキテクチャの一部として指定されていない可能性がありますが、CPU チップの特定のエディションのプロパティ、または特定のコア ロジックまたはマザーボード設計のプロパティでさえあります。
「これをアトミックに行うにはどうすればよいか」に関しては、通常、(より高価な) 交渉による相互排除に頼るのではなく、これを迅速に行う方法があります。これには、衝突を検出する特別な反復可能なコード シーケンスが含まれる場合があります。これらはアセンブリ言語モジュールで実装することをお勧めします。これは、とにかくターゲット固有であるため、HLL への移植性の利点がないためです。
最後に、(高価な)ネゴシエートされた相互排除を必要としないアトミック操作は高速であり、したがって便利であり、いずれにせよ移植可能なコードに必要であるため、システムには通常、アセンブリで記述されたライブラリがあり、同様の機能がすでに実装されています。
式がアトミックかどうかは、コンパイラが生成するマシン コードと、それが実行される CPU アーキテクチャのみに依存します。加算が 1 つの機械語命令で達成されない限り、アトミックである可能性は低いです。
Windows を使用している場合は、InterlockedIncrement() API 関数を使用して、保証されたアトミック インクリメントを実行できます。デクリメントなどにも同様の機能があります。
i は C 言語ではアトミックではないかもしれませんが、ほとんどのプラットフォームでは がアトミックであることに注意してください。GNU C ライブラリのドキュメントには次のように記載されています。
実際には、int および int より長くないその他の整数型はアトミックであると想定できます。ポインター型はアトミックであると想定することもできます。それはとても便利です。これらの前提は両方とも、GNU C ライブラリーがサポートするすべてのマシンと、私たちが知っているすべての POSIX システムに当てはまります。
ターゲットと uC/プロセッサのニーモニック セットに大きく依存します。i がレジスタに保持される変数である場合、それをアトミックにすることができます。
あなたの質問に対する答えi
は、 がローカルstatic
変数か、グローバル変数かによって異なります。またはグローバル変数の場合i
、static
いいえ、ステートメントi += 1
はアトミックではありません。ただし、i
がローカル変数の場合、ステートメントは、x86 アーキテクチャで実行されている最新のオペレーティング システムや、おそらく他のアーキテクチャでもアトミックです。@Dan Cristoloveanuは、ローカル変数のケースについては正しい方向に進んでいましたが、他にも言えることがあります。
(以下では、タスク切り替えで完全にスレッド化が実装された x86 アーキテクチャで保護された最新のオペレーティング システムを想定しています。)
これが C コードであることを考えると、構文は、それがある種の整数変数であるi += 1
ことを意味します。ローカル変数の場合、その値は などのレジスタまたはスタックに格納されます。最初に簡単なケースを処理します。たとえば、 の値がレジスタに格納されている場合、C コンパイラはステートメントを次のように変換する可能性が高くなります。i
%eax
i
%eax
addl $1, %eax
もちろん、これはアトミックです。他のプロセス/スレッドは実行中のスレッドの%eax
レジスタを変更できず、スレッド自体は%eax
この命令が完了するまで再度変更できないためです。
の値がi
スタックに格納されている場合、これはメモリのフェッチ、インクリメント、およびコミットがあることを意味します。何かのようなもの:
movl -16(%esp), %eax
addl $1, %eax
movl %eax, -16(%esp) # this is the commit. It may actually come later if `i += 1` is part of a series of calculations involving `i`.
通常、この一連の操作はアトミックではありません。ただし、最新のオペレーティング システムでは、プロセス/スレッドは別のスレッドのスタックを変更できないため、これらの操作は他のプロセスが干渉することなく完了します。したがって、ステートメントi += 1
はこの場合もアトミックです。
いいえ、そうではありません。i の値がいずれかのレジスターにまだロードされていない場合、1 つのアセンブリー命令でロードすることはできません。
通常はありません。
である場合、それi
はvolatile
CPU アーキテクチャとコンパイラに依存します。メイン メモリに 2 つの整数を追加することが CPU でアトミックである場合、その Cステートメントはvolatile int i
.
いいえ、C 標準はアトミック性を保証していません。実際には、操作はアトミックではありません。ライブラリ ( Windows APIなど) またはコンパイラの組み込み関数 ( GCC、MSVC ) を使用する必要があります。
C / C++ 言語自体は、原子性またはその欠如を主張していません。アトミックな動作を確保するには、組み込み関数またはライブラリ関数に依存する必要があります。
その周りにミューテックスまたはセマフォを置くだけです。もちろん、それはアトミックではなく、50 程度のスレッドが同じ変数にアクセスしてインクリメントするテスト プログラムを作成して、自分で確認することができます。