.NET 2.0 メモリ モデルでは、書き込みには常にリリース フェンスが使用されるとよく耳にします。これは本当ですか?
どのモデルを参照しているかによって異なります。
まず、リリースフェンスバリアを正確に定義しましょう。解放セマンティクスでは、命令シーケンス内のバリアの前にある他の読み取りまたは書き込みは、そのバリアの後に移動することは許可されていません。
- ECMA 仕様には、書き込みがこの保証を提供しない緩和されたモデルがあります。
- Microsoft が提供する CLR 実装は、書き込みにリリース フェンス セマンティクスを持たせることでモデルを強化することがどこかで引用されています。
- x86 および x64 アーキテクチャは、書き込みをリリース フェンス バリアにし、読み取りを取得フェンス バリアにすることで、モデルを強化します。
そのため、難解なアーキテクチャ (Windows 8 が対象とする ARM など) で実行される CLI (Mono など) の別の実装では、書き込み時にリリース フェンス セマンティクスが提供されない可能性があります。可能であると言ったが、確実ではないことに注意してください。しかし、さまざまなソフトウェア レイヤーやハードウェア レイヤーなど、使用されているすべてのメモリ モデル間で、コードを真に移植可能にしたい場合は、最も弱いモデルをコーディングする必要があります。つまり、ECMA モデルに照らしてコーディングし、仮定を行わないということです。
使用中のメモリ モデル レイヤーのリストを明示的に作成する必要があります。
- コンパイラ: C# (または VB.NET など) は命令を移動できます。
- ランタイム: 明らかに、JIT コンパイラを介した CLI ランタイムは命令を移動できます。
- ハードウェア: もちろん、CPU とメモリのアーキテクチャも影響します。
これは、明示的なメモリバリアやロックがなくても、作成されたスレッドとは異なるスレッドで部分的に構築されたオブジェクト (参照型のみを考慮) を観察することは不可能であることを意味しますか?
はい (認定済み): アプリケーションが実行されている環境が十分にわかりにくい場合、部分的に構築されたインスタンスが別のスレッドから観察される可能性があります。これが、 を使用しないとダブルチェックされたロック パターンが安全でない理由の 1 つvolatile
です。ただし、実際には、Microsoft の CLI の実装では命令をこのように並べ替えないため、これに遭遇することはないと思います。
次のコードを使用して、「John 20」と「Jack 21」以外の出力、たとえば「null 20」または「Jack 0」を観察することは可能でしょうか?
繰り返しますが、それは資格があります。しかし、上記の何らかの理由で、あなたがそのような行動を観察することはないと思います。
ただし、person
はマークされvolatile
ていないため、読み取りスレッドが常にnull
. ただし、実際には、この呼び出しにより、C# および JIT コンパイラは、読み取りをループConsole.WriteLine
の外に移動する可能性のあるリフティング操作を回避することになるでしょう。person
このニュアンスについては、すでに十分に認識されていると思います。
これは、非常に不変な参照型のすべての共有フィールドを揮発性にし、(ほとんどの場合) 仕事を続けることができるという意味ですか?
私は知らない。それはかなり負荷の高い質問です。その背後にある文脈をよりよく理解せずに、どちらの方法で答えても安心できません。私が言えることは、通常、操作、、、、などのより明示的なメモリ命令を優先して使用することを避けているということvolatile
です。繰り返しになりますが、ロックなしのコードを完全に回避して、.Interlocked
Thread.VolatileRead
Thread.VolatileWrite
Thread.MemoryBarrier
lock
アップデート:
私が好きな視覚化の方法の 1 つは、C# コンパイラ、JITer などが可能な限り積極的に最適化すると仮定することです。つまりPerson.ctor
、次の疑似コードを生成する (単純なので) インライン化の候補になる可能性があります。
Person ref = allocate space for Person
ref.Name = name;
ref.Age = age;
person = instance;
DoSomething(person);
また、書き込みには ECMA 仕様のリリース フェンス セマンティクスがないため、他の読み取りと書き込みは、割り当てを超えて「フロート」してperson
、次の有効な一連の命令を生成する可能性があります。
Person ref = allocate space for Person
person = ref;
person.Name = name;
person.Age = age;
DoSomething(person);
したがって、この場合、person
初期化される前に割り当てられることがわかります。これは、実行中のスレッドの観点から見ると、論理シーケンスが物理シーケンスと一貫しているため有効です。意図しない副作用はありません。しかし、明らかな理由から、このシーケンスは別のスレッドにとって悲惨なことになります。