Почему С# volatile защищает переупорядочение записи?
В соответствии с этой онлайн-книгой ключевое слово volatile
в С# не защищает от переупорядочения операций записи, за которыми следуют операции чтения. Он дает этот пример, в котором оба a
и b
могут быть установлены на 0
, несмотря на то, что x
и y
являются volatile
:
class IfYouThinkYouUnderstandVolatile
{
volatile int x, y;
void Test1() // Executed on one thread
{
x = 1; // Volatile write (release-fence)
int a = y; // Volatile read (acquire-fence)
...
}
void Test2() // Executed on another thread
{
y = 1; // Volatile write (release-fence)
int b = x; // Volatile read (acquire-fence)
...
}
}
Это похоже на то, что в спецификации указано в 10.5.3:
Чтение изменчивого поля называется изменчивым чтением. Неустойчивое чтение имеет "приобретать семантику"; то есть он гарантированно будет происходить до любых ссылок на память, которые происходят после него в последовательности команд.
Запись изменчивого поля называется volatile write. У изменчивой записи есть "семантика выпуска"; то есть это гарантировано произойдет после любых ссылок на память до команды записи в последовательности команд.
В чем причина этого? Есть ли прецедент, в котором мы не против перезаписывания операций Write-Read?
Ответы
Ответ 1
Volatile не гарантирует, что чтения и записи не будут перенаправлены, это только гарантирует, что чтение получает самое последнее значение (не кэшированное).
http://msdn.microsoft.com/en-us/library/x13ttww7%28v=vs.71%29.aspx
Система всегда считывает текущее значение изменчивого объекта в запрошенной точке, даже если предыдущая команда запрашивала значение от одного и того же объекта. Кроме того, значение объекта записывается сразу при назначении.
Изменчивый модификатор обычно используется для поля, к которому обращаются несколько потоков, без использования оператора блокировки для сериализации доступа. Использование изменчивого модификатора гарантирует, что один поток извлекает самое современное значение, написанное другим потоком.
Всякий раз, когда у вас есть несколько зависимых операций, вам нужно использовать другой механизм синхронизации. Обычно используйте lock
, это проще всего и только создает узкие места производительности при злоупотреблении или в очень экстремальных ситуациях.
Ответ 2
Может быть, слишком поздно, чтобы ответить... но я оглядывался и видел это.
Volatile-read - это вызов метода, аналогичного следующему:
public static int VolatileRead(ref int address)
{
int num = address;
Thread.MemoryBarrier();
return num;
}
И volatile-write выглядит так:
public static int VolatileWrite(ref int address, int value)
{
Thread.MemoryBarrier();
adrdress = value;
}
Инструкция MemoryBarrier();
- это та, которая предотвращает переупорядочение. MemoryBarrier();
гарантирует, что инструкции перед выполнением перед выполнением инструкций после.
Когда VW затем VR, вы будете иметь:
Thread.MemoryBarrier();
adrdress = value; //this line may be reordered with the one bellow
int num = address;//this line may be reordered with the one above
Thread.MemoryBarrier();
return num;
Ответ 3
Предотвращение volatile write
с переупорядочением volatile read
будет довольно дорогостоящим в архитектуре x86/x64. Это из-за оптимизации записи называется store buffers
.
Java пошла по этому пути, и волатильная запись в Java фактически полностью перекрывает память на уровне инструкций процессора.