Как заморозить эскимо в .NET (сделать класс неизменным)

Я разрабатываю класс, который я хочу сделать доступным только для чтения после того, как основной поток завершит его настройку, то есть "заморозит" его. Эрик Липперт называет этоэскимо неизменность. После замораживания к нему могут одновременно обращаться несколько потоков для чтения.

Мой вопрос заключается в том, как написать это потокобезопасным способом, которыйреалистично эффективный, т. е. не пытаясь быть излишнеумная.

Попытка 1:

public class Foobar
{
   private Boolean _isFrozen;

   public void Freeze() { _isFrozen = true; }

   // Only intended to be called by main thread, so checks if class is frozen. If it is the operation is invalid.
   public void WriteValue(Object val)
   {
      if (_isFrozen)
         throw new InvalidOperationException();

      // write ...
   }

   public Object ReadSomething()
   {
      return it;
   }
}

Эрик Липперт, кажется, предполагает, что это будет хорошо вэто Почта. Я знаю, что записи имеют семантику релиза, но, насколько я понимаю, это относится только кзакази это не обязательно означает, что все потоки увидят значение сразу после записи. Кто-нибудь может это подтвердить? Это означало бы, что это решение не является потокобезопасным (это, конечно, не единственная причина).

Попытка 2:

Выше, но с использованиемInterlocked.Exchange чтобы убедиться, что значение действительно опубликовано:

public class Foobar
{
   private Int32 _isFrozen;

   public void Freeze() { Interlocked.Exchange(ref _isFrozen, 1); }

   public void WriteValue(Object val)
   {
      if (_isFrozen == 1)
         throw new InvalidOperationException();

      // write ...
   }
}

Преимущество здесь заключается в том, что мы гарантируем, что значение публикуется без лишних затрат при каждом чтении. Если ни одна из операций чтения не будет перемещена перед записью в _isFrozen, поскольку метод Interlocked использует полный барьер памяти, я думаю, это потокобезопасно. Тем не менее, кто знает, что будет делать компилятор (и в соответствии с разделом 3.10 спецификации C #, которая выглядит довольно много), так что я не знаю, является ли это потокобезопасным.

Попытка 3:

Также сделайте чтение, используяInterlocked.

public class Foobar
{
   private Int32 _isFrozen;

   public void Freeze() { Interlocked.Exchange(ref _isFrozen, 1); }

   public void WriteValue(Object val)
   {
      if (Interlocked.CompareExchange(ref _isFrozen, 0, 0) == 1)
         throw new InvalidOperationException();

      // write ...
   }
}

Определенно потокобезопасен, но кажется немного расточительным делать обмен сравнения для каждого чтения. Я знаю, что эти накладные расходы, вероятно, минимальны, но я ищуразумно эффективный метод (хотя, возможно, это оно).

Попытка 4:

С помощьюvolatile:

public class Foobar
{
   private volatile Boolean _isFrozen;

   public void Freeze() { _isFrozen = true; }

   public void WriteValue(Object val)
   {
      if (_isFrozen)
         throw new InvalidOperationException();

      // write ...
   }
}

Но Джо Даффи объявилсайонара летучая", поэтому я не буду считать это решением.

Попытка 5:

Блокировка всего, кажется немного излишним:

public class Foobar
{
   private readonly Object _syncRoot = new Object();
   private Boolean _isFrozen;

   public void Freeze() { lock(_syncRoot) _isFrozen = true; }

   public void WriteValue(Object val)
   {
      lock(_syncRoot) // as above we could include an attempt that reads *without* this lock
         if (_isFrozen)
            throw new InvalidOperationException();

      // write ...
   }
}

Также, кажется, определенно поточно-ориентированный, но имеет больше издержек, чем при использовании подхода с блокировкой, описанного выше, поэтому я предпочел бы попытку 3 над этой.

И тогда я могу придумать еще немного (я уверен, что есть еще много):

Попытка 6: использованиеThread.VolatileWrite а такжеThread.VolatileRead, но это, предположительно, немного на тяжелой стороне.

Попытка 7: использованиеThread.MemoryBarrierкажется слишком маловнутренний.

Попытка 8: создать неизменную копию - не хочу делать это

Подводя итог:

какую попытку вы бы использовали и почему (или как бы вы делали это, если бы совсем иначе)? (т. е. каков наилучший способ публикации значения, которое затем читается одновременно, при этом будучи достаточно эффективным, не будучи чрезмерно «умным»?)подразумевает ли семантика записи «освобождения» модели памяти .NET, что все другие потоки видят обновления (когерентность кэша и т. д.)? Я вообще не хочу слишком много думать об этом, но приятно иметь понимание.

РЕДАКТИРОВАТЬ:

Возможно, мой вопрос не был ясен, но я, в частности, ищу причины того, почему вышеупомянутые попытки являются хорошими или плохими. Обратите внимание, что я говорю здесь о сценарии одного-единственного писателя, который записывает, а затем останавливается перед любыми одновременными чтениями. Я полагаю, что попытка 1 - это нормально, но я бы хотел точно знать, почему (например, мне интересно, можно ли оптимизировать чтение, например). Меня меньше волнует, является ли это хорошей практикой проектирования, но больше меня интересует фактический аспект многопоточности.

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

Я все еще склоняюсь к попытке № 1 быть верным, однако мне бы понравились некоторые авторитетные ответы. Я понимаю, что у x86 сильная модель, но я не хочу (и не должен) кодировать для конкретной архитектуры, в конце концов, это одна из приятных вещей в .NET.

Если вы сомневаетесь в ответе, воспользуйтесь одним из подходов блокировки, возможно, с приведенными здесь оптимизациями, чтобы избежать большого количества споров о блокировке.

Ответы на вопрос(1)

Ваш ответ на вопрос