i++ はスレッドセーフではないと聞きましたが、++i はスレッドセーフですか?

あなたは間違って聞いた。 "i++" かもしれません 特定のコンパイラと特定のプロセッサ アーキテクチャに対してスレッド セーフですが、標準ではまったく義務付けられていません。実際、マルチスレッドは ISO C または C++ 標準の一部ではないため、 (a) 、それがコンパイルされると考えるものに基づいて、スレッドセーフであると見なすことはできません.

++i であることは十分に可能です。 次のような任意のシーケンスにコンパイルできます:

load r0,[i]  ; load memory into reg 0
incr r0      ; increment reg 0
stor [i],r0  ; store reg 0 back to memory

これは、メモリ増分命令を持たない (架空の) CPU ではスレッドセーフではありません。または、スマートで次のようにコンパイルすることもできます:

lock         ; disable task switching (interrupts)
load r0,[i]  ; load memory into reg 0
incr r0      ; increment reg 0
stor [i],r0  ; store reg 0 back to memory
unlock       ; enable task switching (interrupts)

どこで lock 無効にして unlock 割り込みを有効にします。しかし、その場合でも、メモリを共有するこれらの CPU が複数あるアーキテクチャ (lock 1 つの CPU の割り込みのみを無効にできます)。

言語自体 (言語に組み込まれていない場合はそのためのライブラリ) は、スレッドセーフな構成を提供するため、どのマシン コードが生成されるかについての理解 (または誤解) に依存するのではなく、それらを使用する必要があります。

Java synchronized のようなもの と pthread_mutex_lock() (一部のオペレーティング システムでは C/C++ で利用可能) (a) を調べる必要があります。 .

(a) この質問は、C11 および C++11 標準が完成する前に尋ねられました。これらの反復により、アトミック データ型を含むスレッド サポートが言語仕様に導入されました (ただし、スレッドと一般的なスレッドは オプション ですが、 少なくとも C では)


++i と i++ のどちらについても一概には言えません。なんで? 32 ビット システムで 64 ビット整数をインクリメントすることを検討してください。基になるマシンにクワッド ワードの「ロード、インクリメント、ストア」命令がない限り、その値をインクリメントするには複数の命令が必要になり、そのいずれもスレッド コンテキスト スイッチによって中断される可能性があります。

また、++i 常に「値に 1 を追加する」とは限りません。 C のような言語では、ポインターをインクリメントすると、実際には、指している対象のサイズが追加されます。つまり、 i の場合 ++i の 32 バイト構造体へのポインタです。 32 バイトを追加します。ほとんどすべてのプラットフォームには、アトミックな「メモリ アドレスの値をインクリメントする」命令がありますが、すべてのプラットフォームにアトミックな「メモリ アドレスの値に任意の値を追加する」命令があるわけではありません。


どちらもスレッドセーフではありません。

CPU は、メモリを直接計算することはできません。これは、メモリから値をロードし、CPU レジスタで計算を行うことによって、間接的に行われます。

i++

register int a1, a2;

a1 = *(&i) ; // One cpu instruction: LOAD from memory location identified by i;
a2 = a1;
a1 += 1; 
*(&i) = a1; 
return a2; // 4 cpu instructions

++私

register int a1;

a1 = *(&i) ; 
a1 += 1; 
*(&i) = a1; 
return a1; // 3 cpu instructions

どちらの場合も、予測できない i 値をもたらす競合状態があります。

たとえば、2 つの同時 ++i スレッドがあり、それぞれがレジスタ a1、b1 をそれぞれ使用しているとします。そして、次のようにコンテキスト切り替えを実行します:

register int a1, b1;

a1 = *(&i);
a1 += 1;
b1 = *(&i);
b1 += 1;
*(&i) = a1;
*(&i) = b1;

その結果、i は i+2 ではなく、i+1 になり、これは正しくありません。

これを解決するために、最新の CPU は、コンテキストの切り替えが無効になっている間に、ある種の LOCK、UNLO​​CK CPU 命令を提供します。

Win32 では、スレッドセーフのために InterlockedIncrement() を使用して i++ を実行します。ミューテックスに依存するよりもはるかに高速です。