Может ли кто-нибудь дать хорошее объяснение ключевого слова volatile в C#? Какие проблемы он решает, а какие нет? В каких случаях это избавит меня от использования блокировки?Когда следует использовать ключевое слово volatile в C#?
ответ
Я не думаю, что есть лучший человек, чтобы ответить на этот вопрос, чем Eric Lippert (курсив в оригинале):
В C#, «летучий» означает не только «убедитесь, что компилятор и джиттера не выполнять никакого переупорядочения кода или кэширования регистра оптимизации этой переменной ". Это также означает «рассказать процессорам, что делать то, что им нужно, чтобы убедиться, что я читаю последнее значение , даже если это означает, что останавливать другие процессоры и делать , они синхронизируют основную память с их кешами».
Собственно, этот последний бит - это ложь. Истинная семантика неустойчивых чтений и записи значительно сложнее, чем я описал здесь; в фактах они фактически не гарантируют, что каждый процессор останавливает то, что он делает и обновляет кеши в/из основной памяти. Скорее, предоставляют более слабые гарантии о том, как доступ к памяти до и после считывания и можно наблюдать, что записи могут быть упорядочены относительно друг друга. Некоторые операции, такие как создание новой нити, вводящей блокировку, или с использованием одного из семейства взаимосвязанных методов, обеспечивают более сильные гарантии о наблюдении за заказами . Если вы хотите получить более подробную информацию, читайте разделы 3.10 и 10.5.3 спецификации C# 4.0.
Откровенно говоря, Я отговариваю вас от когда-либо создающего нестабильное поле. Неустойчивые поля являются признаком того, что вы делаете что-то совершенно безумное: вы используете , пытаясь читать и записывать одно и то же значение на двух разных потоках , не помещая замок на место. Замки гарантируют, что считывание памяти или , измененное внутри замка, будет постоянным, блокирует гарантию , что только один поток обращается к данному фрагменту памяти за раз, и поэтому on. Число ситуаций, в которых блокировка слишком медленная, очень мала, а вероятность того, что вы собираетесь получить код неправильно , потому что вы не понимаете, что точная модель памяти очень велика. I не пытайтесь написать код с низким уровнем блокировки, за исключением самых простых значений операций блокировки. Я оставляю использование «volatile» до реальных экспертов.
Для дальнейшего чтения См:
Я бы проголосовал за это, если мог. Там много интересной информации, но на самом деле он не отвечает на его вопрос. Он спрашивает об использовании ключевого слова volatile, связанного с блокировкой. В течение довольно долгого времени (до 2.0 RT) ключевое слово volatile было необходимо использовать для правильного создания потока статического поля, если экземпляр поля имел какой-либо код инициализации в конструкторе (см. Ответ AndrewTek). В производственных средах много кода RT RT, а разработчики, которые его поддерживают, должны знать, почему это ключевое слово существует, и если его безопасно удалить. – 2015-01-01 22:43:54
CLR любит оптимизировать инструкции, поэтому при доступе к полю в коде он может не всегда получать доступ к текущему значению поля (это может быть из стека и т. Д.). Маркировка поля как volatile
обеспечивает доступ к текущему значению поля инструкцией. Это полезно, когда значение может быть изменено (в сценарии без блокировки) параллельным потоком в вашей программе или другим кодом, запущенным в операционной системе.
Вы, очевидно, теряете некоторую оптимизацию, но это делает код более простым.
От MSDN: Модификатор volatile обычно используется для поля, к которому обращаются несколько потоков, без использования оператора блокировки для сериализации доступа. Использование изменчивого модификатора гарантирует, что один поток извлекает самое современное значение, написанное другим потоком.
Иногда компилятор оптимизирует поле и использует регистр для его хранения. Если поток 1 выполняет запись в поле, а другой поток обращается к нему, поскольку обновление хранилось в регистре (а не в памяти), второй поток получал устаревшие данные.
Вы можете думать о ключевом слове volatile как о компиляторе «Я хочу, чтобы вы сохранили это значение в памяти». Это гарантирует, что второй поток получит последнее значение.
Если вы хотите, чтобы получить немного больше о том, что технический летучий ключевое слово делает, рассмотрим следующую программу (я использую DevStudio 2005):
#include <iostream>
void main()
{
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
j += i;
}
for (volatile int i = 0 ; i < 100 ; ++i)
{
j += i;
}
std::cout << j;
}
Используя стандарт оптимизированного (разъединение) настройки компилятора, в компилятор создает следующий ассемблер (IA32):
void main()
{
00401000 push ecx
int j = 0;
00401001 xor ecx,ecx
for (int i = 0 ; i < 100 ; ++i)
00401003 xor eax,eax
00401005 mov edx,1
0040100A lea ebx,[ebx]
{
j += i;
00401010 add ecx,eax
00401012 add eax,edx
00401014 cmp eax,64h
00401017 jl main+10h (401010h)
}
for (volatile int i = 0 ; i < 100 ; ++i)
00401019 mov dword ptr [esp],0
00401020 mov eax,dword ptr [esp]
00401023 cmp eax,64h
00401026 jge main+3Eh (40103Eh)
00401028 jmp main+30h (401030h)
0040102A lea ebx,[ebx]
{
j += i;
00401030 add ecx,dword ptr [esp]
00401033 add dword ptr [esp],edx
00401036 mov eax,dword ptr [esp]
00401039 cmp eax,64h
0040103C jl main+30h (401030h)
}
std::cout << j;
0040103E push ecx
0040103F mov ecx,dword ptr [__imp_std::cout (40203Ch)]
00401045 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (402038h)]
}
0040104B xor eax,eax
0040104D pop ecx
0040104E ret
Глядя на выходе, компилятор решил использовать регистр ECX для хранения значения переменной J. Для нелетучего цикла (первый) компилятор назначил i регистру eax. Довольно просто. Есть пара интересных бит, хотя - команда lea ebx, [ebx] - это команда многобайтового nop, так что цикл перескакивает на 16-байтовый адрес выровненной памяти. Другим является использование edx для увеличения счетчика циклов вместо использования команды inc eax. Команда add reg, reg имеет более низкую задержку на нескольких ядрах IA32 по сравнению с инструкцией inc reg, но никогда не имеет более высокой задержки.
Теперь для цикла с счетчиком волатильного контура. Счетчик хранится в [esp], а ключевое слово volatile сообщает компилятору, что значение всегда должно считываться из/записываться в память и никогда не присваиваться регистру. Компилятор даже доходит до того, что при обновлении значения счетчика не выполняется загрузка/приращение/сохранение в виде трех различных шагов (load eax, inc eax, save eax), вместо этого память непосредственно изменяется в одной команде (добавление mem , р). Способ создания кода гарантирует, что значение счетчика циклов всегда актуально в контексте одного ядра процессора. Никакая операция с данными не может привести к повреждению или потере данных (следовательно, не использовать загрузку/инк/хранилище, поскольку значение может меняться во время inc, таким образом, теряется в магазине). Поскольку прерывания могут обслуживаться только после завершения текущей команды, данные никогда не могут быть повреждены даже при неизмененной памяти.
Как только вы вводите второй процессор в систему, ключевое слово volatile не будет защищать данные, обновляемые другим процессором одновременно.В приведенном выше примере вам нужно, чтобы данные были неровными, чтобы получить потенциальное повреждение. Ключевое слово volatile не предотвратит потенциальное повреждение, если данные не могут обрабатываться атомарно, например, если счетчик циклов имел тип long long (64 бит), тогда для обновления значения потребуется две 32-битные операции, в середине который может прерывать и изменить данные.
Таким образом, ключевое слово volatile подходит только для выровненных данных, которые меньше или равны размеру собственных регистров, так что операции всегда являются атомарными.
Ключевое слово volatile было задумано для использования с операциями ввода-вывода, где IO будет постоянно меняться, но имеет постоянный адрес, например, устройство UART с отображением памяти, а компилятор не должен продолжать повторное использование первого значения, считанного с адрес.
Если вы работаете с большими данными или имеете несколько процессоров, вам понадобится система блокировки более высокого уровня (ОС) для правильной обработки доступа к данным.
Это C++, но этот принцип применим к C#. – Skizz 2008-09-16 15:09:15
Не совсем: http://www.ddj.com/hpc-high-performance-computing/212701484 – 2009-01-13 00:33:56
Эрик Липперт пишет, что волатильность на C++ препятствует компилятору выполнять некоторые оптимизации, в то время как в C# volatile дополнительно происходит некоторая связь между других ядер/процессоров, чтобы обеспечить считывание последнего значения. – 2017-01-26 07:00:48
несколько потоков могут получить доступ к переменной. Последнее обновление будет по переменной
Если вы используете .NET 1.1, то при выполнении двойной проверки блокировки требуется ключевое слово volatile. Зачем? Поскольку до .NET 2.0 следующий сценарий мог вызвать второй поток для доступа к ненулевому, но не полностью сконструированному объекту:
- Тема 1 задает вопрос, имеет ли переменная значение null. //if(this.foo == null)
- Тема 1 определяет, что переменная имеет значение null, поэтому вводит блокировку. //lock(this.bar)
- Тема 1 запрашивает AGAIN, если переменная имеет значение null. //if(this.foo == null)
- Тема 1 по-прежнему определяет, что переменная имеет значение null, поэтому она вызывает конструктор и присваивает значение переменной. //this.foo = new Foo();
До этого .NET 2.0 этому процессу присвоен новый экземпляр Foo, прежде чем конструктор завершит работу. В этом случае может возникнуть вторая нить (во время вызова потока 1 к конструктору Foo) и испытать следующее:
- Тема 2 запрашивает, равна ли переменная null. //if(this.foo == null)
- Тема 2 определяет, что переменная не имеет значения null, поэтому пытается ее использовать. //this.foo.MakeFoo()
До .NET 2.0 можно было бы объявить this.foo как летучими, чтобы обойти эту проблему. Начиная с .NET 2.0 вам больше не нужно использовать ключевое слово volatile для выполнения двойной проверки блокировки.
Википедии на самом деле имеет хорошую статью о перепроверили Запирание и кратко затрагивает эту тему: http://en.wikipedia.org/wiki/Double-checked_locking
Компилятор иногда изменяет порядок отчетности в коде, чтобы оптимизировать его. Обычно это не проблема в однопоточной среде, но это может быть проблемой в многопоточной среде. См следующий пример:
private static int _flag = 0;
private static int _value = 0;
var t1 = Task.Run(() =>
{
_value = 10; /* compiler could switch these lines */
_flag = 5;
});
var t2 = Task.Run(() =>
{
if (_flag == 5)
{
Console.WriteLine("Value: {0}", _value);
}
});
При запуске t1 и t2, то не было бы ожидать, никакого вывода или «Value: 10» в качестве результата. Может быть, компилятор переключает линию внутри функции t1. Если t2 выполняется, возможно, что _flag имеет значение 5, но _value имеет 0. Таким образом, ожидаемая логика может быть нарушена.
Чтобы исправить это, вы можете использовать ключевое слово volatile, которое можно применить к полю. Этот оператор отключает оптимизацию компилятора, поэтому вы можете принудительно ввести правильный код в свой код.
private static volatile int _flag = 0;
Вы должны использовать летучий только если вам действительно нужно, потому что он отключает некоторые оптимизации компилятора, это повредит производительности. Он также не поддерживается всеми .NET-языками (Visual Basic не поддерживает его), поэтому он препятствует взаимодействию языков.
- 1. Volatile ключевое слово в C
- 2. volatile ключевое слово в C# и C++
- 3. using volatile ключевое слово
- 4. Когда следует использовать ключевое слово «self»?
- 5. volatile ключевое слово в Java
- 6. volatile ключевое слово для объектов в C++
- 7. Что такое ключевое слово volatile?
- 8. volatile ключевое слово кажется бесполезным?
- 9. Когда использовать ключевое слово Value в C#
- 10. Вы когда-нибудь использовали ключевое слово volatile в Java?
- 11. Когда следует использовать ключевое слово "strictfp" в java?
- 12. Volatile ключевое слово в Java - Уточнение
- 13. Как правильно использовать ключевое слово volatile в Java?
- 14. Когда вы используете ключевое слово volatile в Java?
- 15. Можно ли использовать ключевое слово volatile с Cudafy.NET?
- 16. Когда следует использовать последнее ключевое слово вместо перечисления?
- 17. Когда следует использовать ключевое слово «TYPENAME» при использовании шаблонов
- 18. Как использовать ключевое слово volatile в этой модели?
- 19. Нужно ли мне ключевое слово volatile? (Java)
- 20. Когда использовать ключевое слово «ожидание»
- 21. volatile указатель на volatile char. дополнительное статическое ключевое слово
- 22. Нам действительно нужно ключевое слово VOLATILE в C#?
- 23. Когда следует использовать слово «дамп»?
- 24. C# USING ключевое слово - когда и когда не использовать его?
- 25. Когда ключевое слово "inline" действует в C?
- 26. Когда использовать ключевое слово 'this' в Java?
- 27. Когда использовать новое ключевое слово в mongodb?
- 28. Когда НЕ ИСПОЛЬЗОВАТЬ «это» ключевое слово?
- 29. ключевое слово синхронизации и волатильное ключевое слово
- 30. C# ref ключевое слово
Почему вы хотите сэкономить на использовании блокировки? Незаконные блокировки добавляют к вашей программе несколько наносекунд. Вы действительно не можете позволить себе несколько наносекунд? – 2013-07-08 20:24:55