Мой вопрос заключается в том, как написать это потокобезопасным способом, который будет реалистично эффективным, т. е. не пытаясь быть излишне умным.
Попытка 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 ...
}
}
Попытка 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 ...
}
}
И тогда я могу придумать еще что-то (я уверен, что их гораздо больше):
Попытка 6: используйте Thread.VolatileWrite и Thread.VolatileRead, но это предположительно немного тяжеловато.
Попытка 7: используйте Thread.MemoryBarrier, кажется слишком внутренней.
Попытка 8: создайте неизменяемую копию — не хочу этого делать
Подведение итогов:
- какую попытку вы бы использовали и почему (или как бы вы это сделали, если бы все было по-другому)? (т.е. как лучше всего опубликовать значение после того, как оно затем будет прочитано одновременно, при этом будучи достаточно эффективным, но не слишком «умным»?)
- означает ли семантика «освобождения» записи в модели памяти .NET, что все остальные потоки видят обновления (связность кэша и т. д.)? Обычно я не хочу слишком много думать об этом, но приятно иметь понимание.
Возможно, мой вопрос был неясен, но я ищу, в частности, причины, почему вышеупомянутые попытки хороши или плохи. Обратите внимание, что я говорю здесь о сценарии, когда один-единственный писатель пишет, а затем зависает перед любым одновременным чтением. Я считаю, что попытка 1 хороша, но мне хотелось бы точно знать, почему (например, мне интересно, можно ли как-то оптимизировать чтение).
Меня не столько волнует, является ли это хорошей практикой проектирования, сколько больше о фактическом аспекте многопоточности.
Большое спасибо за ответ на полученный вопрос, но я решил отметить это как ответ сам, потому что чувствую, что данные ответы совсем не отвечают на мой вопрос, и я отвечаю не хочу создавать у кого-либо, посещающего сайт, впечатление, что отмеченный ответ является правильным просто потому, что он был автоматически отмечен как таковой из-за истечения срока действия вознаграждения.
Более того, я не думаю, что за ответ, набравший наибольшее количество голосов, проголосовало подавляющее большинство, недостаточно, чтобы автоматически пометить его как ответ.
Я все еще склоняюсь к тому, чтобы попытка №1 была правильной, однако мне хотелось бы получить несколько авторитетных ответов. Я понимаю, что у x86 надежная модель, но я не хочу (и не должен) писать код для конкретной архитектуры, в конце концов, это одна из приятных особенностей .NET.
Если вы сомневаетесь в ответе, выберите один из подходов к блокировке, возможно, с показанными здесь оптимизациями, чтобы избежать большого количества споров по поводу блокировки.
Подробнее здесь: https://stackoverflow.com/questions/136 ... -immutable
Мобильная версия