Полезны ли изменчивые переменные? Если да, то когда?
ответ этот вопрос заставил меня задуматься о чем-то, что до сих пор не ясно для меня. Давайте сначала предположим, что мы читаем все из этот пост и этот пост.
[начать редактировать] может, это не так очевидно (итальянский юмор?!) но название просто довольно провокационный: конечно, должна быть причина, если volatile
был включен в C#, я просто не могу понять точный.[конец edit]
короче говоря, мы знаем, что у нас есть три инструмента для обмена переменной между потоками:
-
lock
потому что это предотвратит переупорядочивание инструкций. -
volatile
потому что заставит CPU всегда считывать значение из памяти (тогда разные процессоры / ядра не будут кэш это, и они не увидят старых значений). - Interlocked-операций (
Increment
/Decrement
иCompareExchange
), потому что они будут выполнять изменение + задание в одиночная атомная (быстробыстрее, чем, например, операция volatile + lock).
что я не понимаю (ссылка на спецификации C# будет оценена):
- как блокировка предотвратит кэш? Является ли это неявным барьером памяти в критическом разделе?
- изменчивые переменные не могут быть локальными (я читал что-то от Эрика Липперта об этом, но я не могу найти этот пост сейчас, и я не помню его комментариев и - если честно - я даже не очень хорошо понимаю). Это заставляет меня думать, что они не реализованы с помощью
Interlocked.CompareExchange()
а друзья, в чем они отличаются?
что volatile
модификатор будет делать, например, в этом коде?
volatile int _volatileField = 0;
int _normalField = 0;
void test()
{
Interlocked.Increment(ref _normalField);
++_volatileField;
}
[начать редактировать] предыдущий пример включает атомарное чтение + запись, Давайте изменим его на _volatileField = 1;
, здесь я не говорю об атомных операциях. [редактирование]
более того какой компилятор (рядом с предупреждениями) сделаем здесь:
Interlocked.Increment(ref _volatileField);
они кажутся довольно разными вещами (как я себе представляю), но для моего понимания Interlocked.Increment()
операнд должен неявно быть изменчивым (тогда он добавит только атомарный приращение). Как это возможно для энергонезависимых полей? Они тоже подразумевают барьеры? Разве это не сильно вредит производительности (по сравнению с volatile)?
Если volatile
не означает, что барьеры, но другие то почему мы не можем использовать их как на местном переменные? Особенно при использовании, например, в параллельных циклах это значительно повредит производительности (я думаю о небольших функциях с небольшим кодом, которые работают с большим количеством данных, где кэш данных может быть хорошо использован).
[начать редактировать] я обнаружил, что предыдущее предложение было действительно неясным (извините за мой английский). Я имею в виду: если производительность (из volatile
по сравнению с CompareExchange
, где сравнение применимо), лучше (да, мы можем измерить и в некоторых разница сценариев измерима и видима), то почему мы не можем использовать их для локальных переменных? Я думаю о параллельных циклах, которые манипулируют большим количеством данных (где накладные расходы и барьеры могут сильно повредить производительности).[редактирование]
3 ответов
этот вопрос очень запутанный. Дай я попробую разобраться.
полезны ли изменчивые переменные?
да. Команда C# не добавила бы бесполезную функцию.
Если да, то когда?
изменчивые переменные полезны в некоторых высокочувствительных к производительности многопоточных приложениях, где архитектура приложения основана на совместном использовании памяти между потоками.
в редакции кроме того, я отмечаю, что для обычных программистов C# в любой из этих ситуаций должно быть редкостью. Во-первых, эксплуатационные характеристики, о которых мы здесь говорим, составляют порядка десятков наносекунд; в большинстве приложений LOB требования к производительности измеряются в секундах или минутах, а не в наносекундах. Во-вторых, большинство приложений LOB C# могут выполнять свою работу только с небольшим количеством потоков. В-третьих, общая память-плохая идея и причина многих ошибок; LOB-приложения, которые использование рабочих потоков не следует использовать потоки напрямую, а использовать параллельную библиотеку задач, чтобы безопасно инструктировать рабочие потоки выполнять вычисления, а затем возвращать результаты. Рассмотрите возможность использования нового await
ключевое слово в C# 5.0 для облегчения асинхронности на основе задач, а не использования потоков напрямую.
любое использование volatile в приложении LOB является большим красным флагом и должно быть тщательно рассмотрено экспертами и в идеале устранено в пользу более высокого уровня, менее опасного практиковать.
блокировка предотвратит переупорядочивание инструкций.
блокировка описывается спецификацией C# как специальная точка в коде, так что некоторые специальные побочные эффекты гарантированно упорядочиваются определенным образом в отношении входа и выхода из замка.
volatile, потому что заставит CPU всегда считывать значение из памяти (тогда разные процессоры/ядра не будут кэшировать его, и они не будут видеть старые значения.)
то, что вы описываете, - это детали реализации для того, как volatile может быть реализован; нет требование этот volatile будет реализован путем отказа от кэшей и возврата к основной памяти. Требования к volatile прописаны в спецификации.
заблокированные операции выполняют изменение + назначение в одной атомной (быстрой) операции.
мне непонятно, почему у вас есть в скобках " быстрый "после" атомарный";" быстрый "не является синонимом"атомарный".
как блокировка предотвратит проблему кэша?
снова: блокировка документируется как специальное событие в коде; компилятор необходим для обеспечения того, чтобы другие специальные события имели определенный порядок относительно блокировки. Как компилятор решает реализовать эту семантику, является детализацией реализации.
является ли это неявным барьером памяти в критическом секция?
на практике да, замок вводит полный забор.
изменчивые переменные не могут быть локальными
правильно. Если вы обращаетесь к локальному из двух потоков, то локальный должен быть специальным локальным: это может быть закрытая внешняя переменная делегата, или в асинхронном блоке, или в блоке итератора. Во всех случаях локальное фактически реализуется как поле. Если вы хотите, чтобы такая вещь была изменчивой, не используйте high-level такие функции, как анонимные методы, асинхронные блоки или блоки итераторов! Это смешивание самого высокого уровня и самого низкого уровня кодирования C#, и это очень странно. Напишите свой собственный класс закрытия и сделайте поля изменчивыми, как вы считаете нужным.
Я читал что-то от Эрика Липперта об этом, но я не могу найти этот пост сейчас, и я не помню его ответа.
Ну, я тоже этого не помню, поэтому я набрал " Eric Lippert почему не может быть локальной переменной volatile " в поисковую систему. Это привело меня к следующему вопросу:--3-->
почему локальная переменная не может быть изменчивой в C#?
возможно, это то, о чем вы думаете.
Это заставляет меня думать, что они не реализованы с блокировкой.CompareExchange() и друзей.
C# реализует volatile поля как volatile поля. Летучие поля являются фундаментальной концепцией в среде CLR; как CLR реализует они являются деталью реализации CLR.
в чем они разные?
Я не понимаю вопроса.
какой модификатор volatile будет делать, например, в этом коде?
++_volatileField;
это не делает ничего полезного, поэтому не делайте этого. Изменчивость и атомарность-совершенно разные вещи. Выполнение нормального приращения на летучем поле не делает приращение атомарным прирост.
более того, что компилятор (помимо предупреждений) будет делать здесь:
компилятор C# действительно должен подавить это предупреждение, Если вызываемый метод вводит забор, как это делает этот. Мне так и не удалось вставить это в компилятор. Надеюсь, команда когда-нибудь это сделает.
летучее поле будет обновлено атомарным способом. Забор будет введен приращением, поэтому тот факт, что летучие полу-заборы пропущены смягченный.
Как это возможно для non volatile поля?
это деталь реализации среды CLR.
они подразумевают барьеры тоже?
да, взаимосвязанные операции вводят барьеры. Опять же, это деталь реализации.
не сильно ли это вредит производительности (по сравнению с volatile)?
во-первых, сравнивая производительность сломанного кода для работы кода-это пустая трата времени.
во-вторых, если вы чувствуете, как тратить время, вы прекрасно можете измерить производительность каждого из них самостоятельно. Напишите код в обе стороны, достаньте секундомер, запустите его триллион раз в каждую сторону, и вы узнаете, что быстрее.
Если volatile не подразумевает барьеров, но другие делают, то почему мы не можем использовать их как на локальных переменных?
Я даже не могу начать понимать этот вопрос.
volatile переменные theortically может быть полезно с помощью подобного кода:
while (myVolatileFlag)
...
если myVolatileFlag
объявлен volatile bool
, Это предотвратит кэширование компилятором его значения и предположение, что оно не изменится во время цикла. (Однако на самом деле довольно сложно написать код, который фактически демонстрирует разницу в применении volatile
делает.)
от http://msdn.microsoft.com/en-us/LIBRARY/x13ttww7%28v=vs.80%29.aspx
ключевое слово volatile указывает, что поле может быть изменено несколько одновременно выполняющихся потоков. Поля, которые объявлены volatile не подлежат оптимизации компилятора, которые предполагают доступ одной нитью. Это гарантирует, что наиболее актуальное значение постоянно присутствует в поле.
вот пример программы, которая демонстрирует вопрос:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Demo
{
internal class Program
{
private void run()
{
Task.Factory.StartNew(resetFlagAfter1s);
int x = 0;
while (flag)
++x;
Console.WriteLine("Done");
}
private void resetFlagAfter1s()
{
Thread.Sleep(1000);
flag = false;
}
private volatile bool flag = true;
private static void Main()
{
new Program().run();
}
}
}
запустите сборку "Release" вышеуказанной программы, и она завершится через одну секунду. Удалить volatile
модификатор от volatile bool flag
, и это никогда не закончится.
Летучие Местные Жители
вообще говоря, volatile не требуется для locals, потому что компилятор может видеть, изменяете ли вы local или передаете ссылку на local другому методу. В обоих случаях компилятор будет считать, что значение изменено и отключит оптимизации, которые зависят от значения, не изменяющегося.
однако, с более поздними версиями C# с Lambdas и т. д., Все не так ясно. см. ответ от Эрика Липперта в этой теме.
как
lock
предотвратит проблему кэша? Это неявный барьер памяти в критической секции?
да lock
также действует как полный забор (он имеет как семантику приобретения, так и выпуска). на этой странице в центре разработки Windows объясняется, как это работает:
процессоры могут поддержать инструкции для барьеров памяти с приобретают, освобождение и семантика ограждения. Эти семантики описывают порядок в какие результаты операция становится доступной. С приобрести семантика результаты работы предоставляются до результаты любой операции, которая появляется после нее в коде. С релиз семантика, результаты деятельности доступны после результаты любой операции, которая появляется перед ним в коде. забор семантика объединить семантику приобретения и выпуска. Результаты деятельность с семантикой загородки доступна перед теми из любой операция, которая появляется после нее в коде и после любой операция, которая появляется перед ним.
volatile переменные не могут быть локальными (я читал что-то от Эрика Липперта об этом, но я не могу найти этот пост сейчас и я не помню его ответ.) Это заставляет меня думать, что они не выполнены с Сблокированный.CompareExchange () и друзья, в чем они отличаются?
что volatile модификатор будет делать например в этом код?
они отличаются тем, что они не предотвращают условия гонки с участием других потоков, работающих на той же памяти. Чтение / изменение / магазин с участием volatile
поле не будет атомарным в целом хотя каждый из трех шагов будет (C# имеет выбрал гарантию атомарность для летучих чтения и записи).
на volatile
на примере кода не будет делать много. Он будет убедиться, что при чтении / изменении / хранении последовательность приращения _volatileField
начинается чтение фактически перейдет в память вместо того, чтобы быть удовлетворенным из кэша процессора, но это не поможет вообще с условиями гонки, если есть другие потоки, которые одновременно пишут в поле.