Обычно использование ключевого слова volatile
может вводить в заблуждение.Его цель - разрешить, чтобы последнее значение (или фактически достаточно свежее значение) 1 соответствующего члена возвращалось при доступе из любого потока.
Фактически, это верно для типов значений только 2 .Члены ссылочного типа представлены в памяти как указатели на место в куче, где объект фактически хранится.Таким образом, при использовании ссылочного типа volatile
гарантирует, что вы получите только новое значение ссылки (указателя) на объект, а не сам объект.
Если у вас есть volatile List<String> myVolatileList
, который изменено несколькими потоками с добавлением или удалением элементов, и если вы ожидаете, что он безопасно получит доступ к последней модификации списка, вы на самом деле не правы .На самом деле вы подвержены тем же проблемам, что и если бы не было ключевого слова volatile - условий гонки и / или повреждения экземпляра объекта - это не поможет вам в этом случае и не обеспечит вам никакой безопасности потока.
Если, однако, сам список не изменен различными потоками, а, скорее, каждый поток будет только назначать другой экземпляр для поля (то есть список ведет себя как неизменныйобъект), тогда вы в порядке.Вот пример:
public class HasVolatileReferenceType
{
public volatile List<int> MyVolatileMember;
}
Следующее использование корректно в отношении многопоточности, поскольку каждый поток заменяет указатель MyVolatileMember
.Здесь volatile
гарантирует, что другие потоки увидят последний экземпляр списка, сохраненный в поле MyVolatileMember
.
HasVolatileReferenceTypeexample = new HasVolatileReferenceType();
// instead of modifying `example.MyVolatileMember`
// we are replacing it with a new list. This is OK with volatile.
example.MyVolatileMember = example.MyVolatileMember
.Where(x => x > 42).ToList();
Напротив, приведенный ниже код подвержен ошибкам, поскольку он непосредственно изменяет список.Если этот код выполняется одновременно с несколькими потоками, список может быть поврежден или вести себя непоследовательно.
example.MyVolatileMember.RemoveAll(x => x <= 42);
Давайте ненадолго вернемся к типам значений.В .NET все типы значений на самом деле переназначаются , когда они изменяются, их можно безопасно использовать с ключевым словом volatile
- см. Код:
public class HasVolatileValueType
{
public volatile int MyVolatileMember;
}
// usage
HasVolatileValueType example = new HasVolatileValueType();
example.MyVolatileMember = 42;
1 Понятие позднего значения здесь немного вводит в заблуждение, как отмечает Эрик Липперт в разделе комментариев .Фактически, последнее здесь означает, что среда выполнения .NET будет пытаться (здесь нет никаких гарантий), чтобы предотвратить запись в энергозависимые элементы между операциями чтения всякий раз, когда она считает это возможным.Это способствовало бы тому, чтобы разные потоки считывали новое значение энергозависимого элемента, поскольку их операции чтения, вероятно, будут упорядочены после операции записи в элемент.Но на вероятность здесь можно рассчитывать больше.
2 Как правило, volatile
можно использовать с любым неизменным объектом, поскольку изменения всегда подразумевают переназначение.поля с другим значением.Следующий код также является правильным примером использования ключевого слова volatile
:
public class HasVolatileImmutableType
{
public volatile string MyVolatileMember;
}
// usage
HasVolatileImmutableType example = new HasVolatileImmutableType();
example.MyVolatileMember = "immutable";
// string is reference type, but is *immutable*,
// so we need to reasign the modification result it in order
// to work with the new value later
example.MyVolatileMember = example.MyVolatileMember.SubString(2);
Я бы порекомендовал вам взглянуть на эту статью .В нем подробно объясняется использование ключевого слова volatile, как оно работает на самом деле и возможные последствия его использования.