私が読んだことによると、Intel プロセッサ アーキテクチャは、.net 実装が提供する必要があるよりも強力なメモリ モデルを強制します。コードが Intel プロセッサの保証をどの程度利用するのが適切か、またはコードがより弱いプロセッサを備えたプラットフォームに移行された場合に備えて、Intel の実装には必要のないメモリ バリアをコードがどの程度追加する必要があるか。メモリモデル? 「弱いメモリモデルを使用している場合にメモリバリアを実行する」などのメソッドを使用して静的クラスを定義し、必要に応じてそのライブラリの「強いモデル」または「弱いモデル」バージョンのいずれかにコードをリンクすることを要求するのは適切でしょうか? あるいは、リフレクションを使用して、プログラムの起動時にそのような静的クラスを生成することもできます。これにより、強力なモデルを使用する場合に JIT コンパイラが「
MemoryLock
ドラザーがいる場合、.net は、セミロックを保持するすべてのスレッドがそのセミロックのメモリモデルに従う必要があるいくつかのセミロック操作を備えたクラスのバリエーションを提供します。非常に強力なメモリ モデルを持つシステムでは、セミロックは何もしません。メモリ モデルが非常に弱いシステムでは、既に別のスレッドが含まれているセミロックに入ろうとするスレッドは、最初のスレッドが終了するか、CPU またはコア (ベース) でスケジュールされるまで待機する必要があります。最初のスレッドが使用していたセミロックで指定されたモデルに基づいて)。通常のロックとは異なり、MemoryLock
すべてのスレッドを同じ CPU で実行するようにスケジュールすることで、競合するロック要件の組み合わせを解決でき、システムは停止したスレッドによって保持されているものを解放できるため、デッドロックが発生することはありません(リソースへのアクセスを保護することがMemoryLock
目的であるため) 。MemoryLock
メモリモデルに違反する方法で、デッドスレッドはもちろんそのようなアクセスを行うことはできません)。
もちろん、.net 4.0 の時点ではそのようなものは存在しません。それを考えると、存在する状況を処理する最善の方法は何ですか? より強力なメモリ モデル用に設計されたコードをより弱いモデルのシステムに移行することは、より強力なモデルを強制する何らかの手段がない場合、災害のレシピとなりますが、多くのLock
またはMemoryBarrier
コードの元のターゲット プラットフォームにとって不要な呼び出しは、あまり魅力的ではないようです。私が知っているコードで強力なメモリ モデルを強制する唯一の方法は、各スレッドに CPU アフィニティを設定させることです。.net が一度に 1 つのコアのみを使用するようにプロセス オプションを設定する方法があれば、それは役立つかもしれません (特に、JIT がバス ロックのインターロック操作をより高速な非バス ロックの同等のものに置き換えることができることを意味する場合)。 、しかし、CPUアフィニティを設定する唯一の方法は、そのCPUが他のアプリケーションによって非常に負荷が高く、他のCPUがアイドル状態にある場合でも、プログラムがすべてのスレッドに対して特定の選択されたCPUを使用するように制限することです.
補遺
次のコードを検討してください。
// スレッド 1 -- 開始時に SharedPerson が Person "Smiley"、"George" を指していると仮定します var newPerson = new Person(); newPerson.LastName = "シンプソン"; newPerson.FirstName = "バート"; // MaybeMemoryBarrier1 SharedPerson = newPerson; // スレッド 2 var wasPerson = SharedPerson; // MaybeMemoryBarrier2 var wasLastName = wasPerson.FirstName; var WasFirstName = wasPerson.LastName;
私の理解では、メモリバリアがなくても、Intel プロセッサで実行されているコードは、書き込みが再配列されないことを保証します。したがって、スレッド 2 では、「Smiley」、「George」、または「Simpson」、「Bart」のいずれかが読み取られます。ただし、.net メモリ モデルはそれよりも弱く、スレッド 2 が不完全なオブジェクトを参照する可能性があるプロセッサ上で .net プログラムが実行されていることに気付く可能性があります ( への書き込みが へSharedPerson
の書き込みの前に発生する可能性があるためnewPerson.FirstName
)。にメモリ バリアを追加するMaybeMemoryBarrier1
と、その危険を回避できますが、実際に必要かどうかにかかわらず、メモリ バリアにはパフォーマンス コストがかかります。
最小要件の .net メモリ モデルは、スレッド 2 がそれ自体を読み取る前にMaybeMemoryBarrier2
によって参照されるオブジェクトに決してアクセスしないことが保証されている場合に必要なほど弱いとは思いません(上記のコードの場合のように、新しいインスタンスは、に格納される前に外部コードに公開されないため)。一方、状況が少し変わったので、レコードを作成し、それをキューに入れたとします(キュー自体に必要なすべてのロックとメモリ バリアがあると仮定します)。その後、プロセッサは次のことを行います。SharedPerson
SharedPerson
SharedPerson
Thread 2
JobInfo
Thread 1
// スレッド 1 var newJob = JobQueue.GetJob(); // Thread2 によって書き込まれた JobInfo を取得します newJob.StartTime = DateTime.Now(); // 8 バイトの構造体はキャッシュ ラインにまたがる可能性があります // 一度書き込んだら変更されない // MaybeMemoryBarrier1 現在のジョブ = 新しいジョブ; // スレッド 2 var wasJob = 現在のジョブ; // MaybeMemoryBarrier2 var wasStartTime = CurrentJob.StartTime();
スレッド 1 にメモリ バリアがあり、スレッド 2 にはない場合、スレッド 2 がJobInfo
作成したレコードが に表示されるのを確認したときにCurrentJob
、そのStartTime
フィールドを正しく読み取る (キャッシュされた値または部分的にキャッシュされた値が残っているのを確認しない) という保証はありますか?Thread 2
あの物体を操作していた時から?