4

別のオブジェクトへの参照を持つクラスを設計する場合、参照されるオブジェクトを最初に使用するときにのみ作成することが有益な場合があります。たとえば、遅延読み込みを使用します。

私はよくこのパターンを使用して、遅延ロードされたプロパティを作成します。

Encoding utf8NoBomEncoding;

Encoding Utf8NoBomEncoding {
  get {
    return this.utf8NoBomEncoding ?? 
      (this.utf8NoBomEncoding = new UTF8Encoding(false));
  }
}

次に、BCL のソース コードを参照しているときに、次のコードを見つけました。

Encoding Utf8NoBomEncoding {
  get {
    if (this.utf8NoBomEncoding == null) {
      var encoding = new UTF8Encoding(false);
      Thread.MemoryBarrier();
      this.utf8NoBomEncoding = encoding;
    }
    return this.utf8NoBomEncoding;
  }
}

私が知る限り、これらはどれもスレッドセーフではありません。たとえば、複数のEncodingオブジェクトを作成できます。Encoding私はそれを完全に理解しており、余分なオブジェクトが作成されても問題ないことを知っています。これは不変であり、まもなくガベージ コレクションになります。

Thread.MemoryBarrierただし、なぜが必要なのか、またマルチスレッド シナリオで 2 番目の実装が最初の実装とどのように異なるのかを理解したいと思っています。

明らかに、スレッドの安全性が懸念される場合、最適な実装はおそらく次を使用することLazy<T>です。

Lazy<Encoding> lazyUtf8NoBomEncoding = 
  new Lazy<Encoding>(() => new UTF8Encoding(false));

Encoding Utf8NoBomEncoding {
  get {
    return this.lazyUtf8NoBomEncoding.Value;
  }
}
4

2 に答える 2

6

このコードは、メモリバリアがなければ惨事になります。これらのコード行をよく見てください。

  var encoding = new UTF8Encoding(false);
  Thread.MemoryBarrier();
  this.utf8NoBomEncoding = encoding;

ここで、他のスレッドが最後の行の効果を認識しているが、最初の行の効果を認識していないと想像してください。それは完全な災害になります。

メモリ バリアは、すべてのスレッドがencodingコンストラクタのすべての効果も確認できるようにします。

たとえば、メモリ バリアがない場合、最初と最後の行は、次のように内部的に (大まかに) 最適化できます。
1) メモリを割り当て、そのポインタを this.utf8NoBomEncoding に
格納します。有効な値。

ステップ 1 と 2 の間に別のスレッドが実行され、このコードを通過する場合を想像してください。まだ構築されていないオブジェクトを使用します。

于 2011-11-09T15:35:05.267 に答える
2

このパターンは、.NET ではかなり一般的です。UTF8Encoding は不変クラスなので可能です。はい、クラスの複数のインスタンスを作成できますが、すべてのインスタンスが同じであるため問題ありません。Equals() オーバーライドで強制されます。余分なコピーはすぐにガベージ コレクションされます。メモリバリアは、オブジェクトの状態が完全に見えるようにするだけです。

于 2011-11-09T15:54:46.560 に答える