2

私が読んだことによると、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によって参照されるオブジェクトに決してアクセスしないことが保証されている場合に必要なほど弱いとは思いません(上記のコードの場合のように、新しいインスタンスは、に格納される前に外部コードに公開されないため)。一方、状況が少し変わったので、レコードを作成し、それをキューに入れたとします(キュー自体に必要なすべてのロックとメモリ バリアがあると仮定します)。その後、プロセッサは次のことを行います。SharedPersonSharedPersonSharedPersonThread 2JobInfoThread 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あの物体を操作していた時から?

4

2 に答える 2

1

TL;DR: .net メモリ モデルに対してのみコードを記述する必要があります。強くない。

x86 アーキテクチャが .net で説明されているものよりも強力なメモリ モデルを持っていることは事実です。

ただし、コードを他のプラットフォーム (ARM など) に移植する予定がない場合でも、x86 メモリ モデルの観点から考える必要はありません。コンパイラと JITer は、x86 モデルを破る最適化を自由に実行できるためです。したがって、Intel CPU でも安全ではありません。

たとえば、JIT は、この例で newPerson ローカル変数を完全に回避することを決定できます。これは、次のコードと同等です。

SharedPerson = new Person();
SharedPerson.LastName = "Simpson";
SharedPerson.FirstName = "Bart";

これがどれほど壊れているか分かりますか?以前に初期化された SharedPerson を使用しても、スレッド 2 は FirstName と LastName == null を参照できます (設定される前に読み取った場合)。この最適化は完全に合法であり、シングルスレッドの動作を変更しません。

適切な同期がなければ、ハードウェアとランタイムは、シングルスレッドの動作が変わらない限り、メモリの書き込みと読み取りを自由に導入/削除/並べ替えできます。

他のスレッドへの参照をアトミックに発行するには、揮発性書き込みを使用する必要があります。SharedPerson が volatile の場合、コードは問題ありません (明示的なメモリ バリアを追加する必要はありません)。また、x86 では、揮発性書き込みは単なる通常の書き込みであるため、「無料」で提供されることに注意してください。ランタイムは命令を追加しません。ただし、.net ランタイムによる最適化は禁止されます (揮発性書き込みの後に以前のメモリ操作を移動できないため、上記の例は違法になります。したがって、揮発性書き込みが発生する前に .LastName と .FirstName を割り当てる必要があります)。

于 2013-06-01T19:27:19.243 に答える
0

あなたの理解が正しいとは思えません。.NET メモリ モデルでは、ストアの並べ替えが許可されているようです。つまり、メモリ モデルが非常に弱いいくつかの存在しない CPU では、SharedPerson がスレッド 1 によって格納されFirstNameLastNameメンバーが格納される前に、"Bart"/null または null/ が発生する可能性があります。 「シンプソン」、または null/null です。しかし、thread1 が実行中にスレッド 2 がローカル参照を作成し、そこから読み取ることを考えると、弱いメモリ モデルが一貫性のない書き込み("George"/"Simpson") になる可能性があるとは思いません。を新しいインスタンスSharedPersonにアトミックに置き換えます。SharedPerson

CLI 仕様には次のように記載されています。

準拠する CLI は、場所へのすべての書き込みアクセスが同じ場合、ネイティブ ワード サイズ (ネイティブ int 型のサイズ) を超えない適切に配置されたメモリ位置への読み取りおよび書き込みアクセスがアトミックであることを保証するものとします (§12.6.2 を参照)。サイズ

とはいえ、私が知る限り、サポートされているプラ​​ットフォームにはそのようなメモリ モデルは存在せず、Chris Brumme のブログ (こちら)も同様のことを示唆しています。

于 2012-06-15T17:04:23.327 に答える