Как заморозить фруктовое мороженое в .NET (сделать класс неизменяемым)C#

Место общения программистов C#
Ответить
Anonymous
 Как заморозить фруктовое мороженое в .NET (сделать класс неизменяемым)

Сообщение Anonymous »

Я разрабатываю класс, который хочу сделать доступным только для чтения после того, как основной поток завершит его настройку, то есть «заморозит» его. Эрик Липперт называет это неизменяемостью эскимо. После того, как он заморожен, к нему могут одновременно обращаться несколько потоков для чтения.
Мой вопрос заключается в том, как написать это потокобезопасным способом, который будет реалистично эффективным, т. е. не пытаясь быть излишне умным.
Попытка 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:
Использование энергозависимого:

Код: Выделить всё

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.
Если вы сомневаетесь в ответе, выберите один из подходов к блокировке, возможно, с показанными здесь оптимизациями, чтобы избежать большого количества споров по поводу блокировки.

Подробнее здесь: https://stackoverflow.com/questions/136 ... -immutable
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C#»