Возможный дубликат:
Являются ли автоматически реализуемые статические свойства C # поточно-ориентированными?
В следующем примере class
static class Shared
{
public static string[] Values { get; set; }
}
многие потоки чтения периодически читают массив строк Values
, в то время как время от времени время от времени один писатель заменит весь массив новым значением с помощью установщика. Нужно ли мне использовать ReaderWriterLock
или это то, что C # будет обрабатывать автоматически?
Правка: в моем случае единственная необходимая «безопасность потока»: ничего плохого не должно произойти, когда писатель заменяет массив, пока читатель ищет ценность. Меня не волнует, будут ли читатели использовать новое значение немедленно, если они будут использовать его в будущем
Это использование потокобезопасно:
string[] localValues = Shared.Values;
for (int index = 0; index < localValues.length; index++)
ProcessValues(localValues[index]);
Такое использование не является потокобезопасным и может привести к исключениям, выходящим за пределы:
for (int index = 0; index < Shared.Values.Length; index++)
ProcessValues(Shared.Values[index]);
Я бы предпочел сделать потокобезопасным вызывает более естественный вызов, делая что-то вроде этого:
static class Shared
{
private static string[] values;
public static string[] GetValues() { return values; }
public static void SetValues(string[] values) { Shared.values = values; }
}
Конечно, пользователи все еще могут помещать GetValues () в циклы, и это было бы так же плохо, но, по крайней мере, это явно плохо.
В зависимости от ситуации, даже лучшим решением может быть раздача копий, чтобы вызывающий код вообще не мог изменять массив. Я обычно так поступаю, но это может не подходить в вашей ситуации.
static class Shared
{
private static string[] values;
public static string[] GetValues()
{
string[] currentValues = values;
if (currentValues != null)
return (string[])currentValues.Clone();
else
return null;
}
public static void SetValues(string[] values)
{
Shared.values = values;
}
}
Я бы заблокировал. Для множественного чтения и случайной записи используйте ReaderWriterLockSlim - гораздо более эффективный, чем ReaderWriteLock .
static class Shared
{
private static ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
private static string[] _values;
public static string[] Values
{
get
{
_rwLock.EnterReadLock();
try
{
return _values;
}
finally
{
_rwLock.ExitReadLock();
}
}
set
{
_rwLock.EnterWriteLock();
try
{
_values = value;
}
finally
{
_rwLock.ExitWriteLock();
}
}
}
}
Это не потокобезопасный . Да, вам нужно будет использовать замок.
Без дополнительной защиты нет гарантии, что читающий поток когда-либо увидит новое значение. На практике, пока читающий поток выполняет какие-либо значимые действия, он увидит новое значение. В частности, вы никогда не увидите "половинного" обновления, когда ссылка указывает на мертвое пространство.
Если вы сделаете поле волатильным
, я полагаю, даже эта опасность будет устранена - но программирование без блокировок вообще трудно поддается рассуждениям. Это означает отказ от автоматически реализуемого свойства, конечно.
Это зависит от того, что вы подразумеваете под потокобезопасностью.
Чтение и замена гарантированно будут атомарными, но не гарантируется, что чтение, следующее за записью, обязательно прочитает новое значение.
В ответ на ваше изменение ...
Ничего плохого не произойдет с вашим существующим кодом (например, разорванное чтение), но нет гарантии, что ваши читатели когда-либо увидят новое значение . Например, возможно - хотя и маловероятно - что старая ссылка будет навсегда кэширована в регистре.
Чтобы немного отклониться от темы, модель памяти Java 2 1.5 добавляет две гарантии (см. Теория и практика Java: Исправление модели памяти Java, Часть 2 ):
final
появляются полностью инициализированными вне конструктора. Последнее представляет собой интересный случай: раньше вы могли выполнять foo = new String (new char [] {'a', 'b', 'c'});
в одном потоке и foo = "123"; System.out.println (foo)
в другом потоке и распечатайте пустую строку, потому что не было гарантии, что запись в последние поля foo произойдет до записи в foo.
Я не уверен в деталях инициализации массива .NET; возможно, вам нужно использовать Thread.MemoryBarrier () (в начале получателя и в конце установщика), чтобы читатели видели только полностью инициализированные массивы.