Отвечая этот вопрос заставлял меня думать о чем-то, все еще не ясен для меня. Предположим сначала, что мы читаем все из этого сообщения и этот пост.
[начать редактирование] Возможно, это не так очевидно (итальянский юмор?!), но заголовок просто довольно провокационный: конечно, должна быть причина, если volatile
был включен в С#, я просто не могу понять точный. [end edit]
Короче говоря, мы знаем, что у нас есть три инструмента для обмена переменной между потоками:
-
lock
, поскольку это предотвратит переупорядочение команд. -
volatile
, потому что заставит CPU всегда читать значение из памяти (тогда разные ЦП/ядра не будут кэшировать его, и они не будут видеть старые значения). - Заблокированные операции (
Increment
/Decrement
иCompareExchange
), потому что они будут выполнять изменение + присваивание в одном атоме (fastбыстрее, чем, например, волатильная + блокировка) работа.
То, что я не понимаю (ссылка на спецификации С# будет оценена):
- Как блокировка предотвратит проблему с кешем? Является ли это неявным барьером памяти в критическом разделе?
- Летучие переменные не могут быть локальными (я читал что-то от Эрика Липперта об этом, но я не могу найти этот пост сейчас, и я не помню его комментариев и, если честно, я даже не понял его Что ж). Это заставляет меня думать, что они не реализованы с
Interlocked.CompareExchange()
и друзьями, в чем они отличаются?
Какой модификатор volatile
будет делать, например, в этом коде?
volatile int _volatileField = 0;
int _normalField = 0;
void test()
{
Interlocked.Increment(ref _normalField);
++_volatileField;
}
[начать редактирование] Предыдущий пример включает в себя атомарное чтение + запись, пусть оно изменится на _volatileField = 1;
, здесь я не говорю об атомных операциях. [end edit]
Более того, какой компилятор (помимо предупреждений) будет делать здесь:
Interlocked.Increment(ref _volatileField);
Они кажутся довольно разными (как я мог себе представить), но для моего понимания Interlocked.Increment()
операнд должен неявно быть volatile (тогда он добавит только атомное приращение). Как это возможно для нелетучих полей? Означает ли они также барьеры? Разве это не сильно ухудшает производительность (по сравнению с изменчивым)?
Если volatile
не подразумевает барьеры, но другие делают то, почему мы не можем использовать их как локальные переменные? Особенно, когда используется, например, параллельные петли, это значительно ухудшит производительность (я думаю о небольших функциях с небольшим кодом, который работает с большим количеством данных, где кеш данных может быть хорошо использован).
Забастовкa >
[начать редактирование] Я нашел предыдущее предложение действительно непонятным (извините за мой английский). Я имею в виду: если производительность (volatile
по сравнению с CompareExchange
, где сравнение применимо) лучше (да, мы можем измерить, а в некоторых случаях разница измерима и видима), то почему мы не можем использовать их для локальных переменные? Я думаю о параллельных циклах, которые манипулируют множеством данных (где и накладные расходы, и барьеры могут сильно повредить производительность). [End edit]