web-dev-qa-db-ja.com

ロックせずに同時に変更されている整数変数を読み取っても安全ですか?

クラスに整数変数があり、この変数が他のスレッドによって同時に変更される可能性があるとします。書き込みはミューテックスによって保護されています。読み取りも保護する必要がありますか?一部のハードウェアアーキテクチャでは、あるスレッドが変数を変更し、別のスレッドがそれを読み取ると、読み取り結果がガベージになると聞きました。この場合、読み取りを保護する必要があります。私はそのようなアーキテクチャを見たことがありません。

この質問は、単一のトランザクションが単一の整数変数の更新のみで構成されていることを前提としているため、トランザクションに関与する可能性のある他の変数の状態について心配する必要はありません。

46
Hongli

atomic read
前述したように、プラットフォームに依存しています。 x86では、値は4バイト境界に整列する必要があります。一般に、ほとんどのプラットフォームでは、読み取りは単一のCPU命令で実行する必要があります。

オプティマイザキャッシング
オプティマイザは、別のスレッドによって変更された値を読み取っていることを認識していません。値volatileを宣言すると、それが役立ちます。オプティマイザは、値をレジスタにキャッシュしておくのではなく、アクセスごとにメモリの読み取り/書き込みを発行します。

CPUキャッシュ
それでも、最新のアーキテクチャでは、自動的に同期されない個別のキャッシュを持つ複数のコアがあるため、古い値を読み取る可能性があります。読み取りメモリバリア、通常はプラットフォーム固有の命令が必要です。

Wintelでは、スレッド同期関数が完全なメモリバリアを自動的に追加するか、または InterlockedXxxx 関数を使用できます。

MSDN: メモリと同期の問題MemoryBarrier マクロ

[編集] drhirschのコメントも参照してください。

34
peterchen

変数の読み取りについて質問し、後で変数の更新について話します。これは、読み取り-変更-書き込み操作を意味します。

あなたが前者を本当に意味すると仮定すると、読み取りは安全ですそれがアトミック操作の場合。ほとんどすべてのアーキテクチャで、これは整数に当てはまります。

いくつかの(そしてまれな)例外があります。

  • たとえば、奇数アドレスで4バイトの整数にアクセスするなど、読み取りが正しく調整されていません。通常、いくつかのミスアライメントを行うには、特別な属性を使用してコンパイラーを強制する必要があります。
  • Intのサイズは、たとえば8ビットアーキテクチャで16ビットintを使用するなど、命令の自然なサイズよりも大きくなります。
  • 一部のアーキテクチャでは、バス幅が人為的に制限されています。私は386sxや68008のような非常に古くて時代遅れのものを知っています。
14
Gunther Piez

この場合、コンパイラやアーキテクチャに依存しないことをお勧めします
リーダーとライターが混在している場合は(リーダーのみまたはライターのみではなく)、それらすべてを同期することをお勧めします。あなたのコードが誰かの人工的な心を動かしていると想像してください、あなたは本当にそれが間違った値を読むことを望まないでしょう、そして誰かがそのミューテックスを使わないことに決めたのであなたの街の発電所が「ブーム」になることを望んでいないでしょう。長い目で見れば、夜の眠りを救うことができます。
スレッドの読み取りが1つしかない場合-その1つのmutexだけで十分ですが、複数のリーダーと複数のライターを計画している場合は、それを同期するための洗練されたコードが必要です。 。 「公正」でもある読み取り/書き込みロックの素晴らしい実装は、私にはまだ見られていません。

8
Dmitry

あるスレッドで変数を読み取っていて、そのスレッドが読み取り中に中断され、書き込みスレッドによって変数が変更されたとします。読み取りスレッドが再開した後の読み取り整数の値は何ですか?

変数の読み取りがアトミック操作でない限り、この場合は1つの(アセンブリ)命令しかとらないため、上記の状況が発生しないことを保証することはできません。 (変数はメモリに書き込むことができ、値を取得するには複数の命令が必要です)

合意は、すべての書き込みを個別にカプセル化/ロックする必要があるということですが、読み取りは他の読み取り(のみ)と同時に実行できます。

5
NomeN

クラスに整数変数があり、この変数が他のスレッドによって同時に変更される可能性があるとします。書き込みはミューテックスによって保護されています。読み取りも保護する必要がありますか?一部のハードウェアアーキテクチャでは、あるスレッドが変数を変更し、別のスレッドがそれを読み取ると、読み取り結果がガベージになると聞きました。この場合、読み取りを保護する必要があります。私はそのようなアーキテクチャを見たことがありません。

一般的なケースでは、それは潜在的にeveryアーキテクチャです。すべてのアーキテクチャには、書き込みと同時に読み取るとガベージが発生する場合があります。ただし、ほとんどすべてのアーキテクチャにもこのルールの例外があります。

Wordサイズの変数はアトミックに読み書きされるのが一般的であるため、またはの書き込みを読み取るときに同期は必要ありません。適切な値は単一の操作としてアトミックに書き込まれ、スレッドはcurrent値も単一のアトミック操作として読み取ります(別のスレッドが書き込んでいる場合でも)。したがって、整数の場合、mostアーキテクチャで安全です。この保証を他のいくつかのサイズにも拡張する人もいますが、それは明らかにハードウェアに依存しています。

非Wordサイズの変数の場合、読み取りと書き込みの両方が通常非原子的であり、他の方法で同期する必要があります。

3
jalf

新規に書き込むときにこの変数の以前の値を使用しない場合は、次のようになります。

ミューテックスを使用せずに整数変数を読み書きできます。これは、整数が32ビットアーキテクチャの基本型であり、値のすべての変更/読み取りが1つの操作で行われるためです。

ただし、インクリメントなどを割り当てる場合:

myvar++;

次に、この構造がmyvar = myvar + 1に拡張され、読み取りmyvarとインクリメントmyvarの間でmyvarを変更できるため、ミューテックスを使用する必要があります。その場合、あなたは悪い値を取得します。

2
Vitaly Dyatlov

同期せずに32ビットシステムでintを読み取っても安全でしょう。私はそれを危険にさらすことはありません。複数の同時読み取りは問題ではありませんが、書き込みが読み取りと同時に行われるのは好きではありません。

読み取りをクリティカルセクションにも配置し、複数のコアでアプリケーションのストレステストを行って、競合が多すぎるかどうかを確認することをお勧めします。並行性のバグを見つけることは、避けたい悪夢です。将来誰かがintをlong longまたはdoubleに変更して、より大きな数を保持できるようにした場合はどうなりますか?

Boost.threadやzthreadのようなNiceスレッドライブラリがある場合は、読み取り/書き込みロックが必要です。これらは、書き込みを保護しながら複数の読み取りを許可するため、状況に最適です。

2
iain

これは、16ビット整数を使用する8ビットシステムで発生する可能性があります。

ロックを回避したい場合は、適切な状況下で、2つの等しい連続する値を取得するまで、複数回の読み取りを回避できます。たとえば、このアプローチを使用して、クロックティックが割り込みルーチンとして実装されている32ビットの組み込みターゲットで64ビットクロックを読み取りました。その場合、時計は読み取りルーチンが実行される短時間に1回しか刻むことができないため、3回の読み取りで十分です。

1
starblue

一般に、各機械語命令は、実行時にいくつかのハードウェアステージを通過します。最新のCPUはマルチコアまたはハイパースレッドであるため、変数を読み取ると、命令パイプラインを介して変数が開始されますが、別のCPUコアまたはハイパースレッドがストア命令を同時に実行するのを停止しません住所。同時に実行される2つの命令である読み取りと保存は、「クロスパス」になる可能性があります。つまり、読み取りは、新しい値が保存される直前に古い値を受け取ります。
再開するには、読み取りと書き込みの両方にミューテックスが必要です。

0
harrymc

並行性を持つ変数への読み取りと書き込みの両方は、(ミューテックスではなく)クリティカルセクションで保護する必要があります。丸一日のデバッグを無駄にしたくない限り。

重要なセクションはプラットフォーム固有のものだと思います。 Win32では、クリティカルセクションは非常に効率的です。インターロックが発生しない場合、クリティカルセクションの入力はほとんど解放され、全体的なパフォーマンスに影響を与えません。インターロックが発生した場合、スレッドを中断する前に一連のチェックを実装するため、mutexよりも効率的です。

0
SadSido