Ленивая загрузка и использование Thread.MemoryBarrier - PullRequest
4 голосов
/ 09 ноября 2011

При разработке класса, который имеет ссылку на другой объект, может быть полезно создавать ссылочный объект только при первом его использовании, например, использовать ленивую загрузку.

Я часто использую этот шаблон для создания ленивого загруженного свойства:

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 необходим и чем вторая реализация отличается от первой в многопоточных сценариях.

Очевидно, что если безопасность потока является проблемой, то лучшая реализация, вероятно, должна использовать Lazy<T>:

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

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

Ответы [ 2 ]

6 голосов
/ 09 ноября 2011

Этот код был бы катастрофой без барьера памяти. Посмотрите внимательно на эти строки кода.

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

Теперь представьте, что другой поток видит эффекты последней строки, но не видит эффекты первой строки. Это было бы полной катастрофой.

Барьер памяти гарантирует, что любой поток, который вообще видит encoding, также видит все эффекты своего конструктора.

Например, без барьера памяти, первая и последняя строки могут быть внутренне оптимизированы (примерно) следующим образом:
1) Выделите немного памяти, сохраните указатель на нее в this.utf8NoBomEncoding
2) Вызовите конструктор UTF8Encoding, чтобы заполнить эту память допустимыми значениями.

Представьте, что между шагами 1 и 2 другой поток запускается и проходит через этот код. Он будет использовать объект, который еще не был построен.

2 голосов
/ 09 ноября 2011

Этот шаблон довольно распространен в .NET.Это возможно, потому что UTF8Encoding является неизменным классом.Да, можно создать более одного экземпляра класса, но это не имеет значения, поскольку все экземпляры одинаковы.Осуществляется с переопределением Equals ().Дополнительные копии будут быстро собраны.Барьер памяти просто гарантирует, что состояние объекта полностью видимо.

...