Как этот пример MSDN CompareExchange не требует нестабильного чтения? - PullRequest
5 голосов
/ 26 сентября 2011

Я искал реализацию счетчика с защитой потока, использующую Interlocked, которая поддерживала увеличение на произвольные значения, и нашел этот образец прямо из документации Interlocked.CompareExchange (слегка измененной для простоты):

private int totalValue = 0;

public int AddToTotal(int addend)
{
    int initialValue, computedValue;
    do
    {
        // How can we get away with not using a volatile read of totalValue here?
        // Shouldn't we use CompareExchange(ref TotalValue, 0, 0)
        // or Thread.VolatileRead
        // or declare totalValue to be volatile?           
        initialValue = totalValue;

        computedValue = initialValue + addend;

    } while (initialValue != Interlocked.CompareExchange(
        ref totalValue, computedValue, initialValue));

    return computedValue;
}

 public int Total
 {
    // This looks *really* dodgy too, but isn't 
    // the target of my question.
    get { return totalValue; }
 }

Я понимаю, что этот код пытается сделать, но я не уверен, как можно обойтись без использования volatile чтения разделяемой переменной при присваивании временной переменной, к которой добавляется.

Есть ли вероятность, что initialValue будет хранить устаревшее значение в цикле, и функция никогда не вернется?Или барьер памяти (?) В CompareExchange исключает такую ​​возможность?Мы будем благодарны за любые идеис последнего CompareExchange вызова этот код будет в порядке.Но это гарантировано?

Ответы [ 3 ]

2 голосов
/ 26 сентября 2011

Управляемый Interlocked.CompareExchange отображается непосредственно на InterlockedCompareExchange в Win32 API (есть также 64-битная версия ).

Как видно из сигнатур функций, нативный API требует, чтобы назначение было энергозависимым, и, хотя это не требуется управляемым API, использование энергозависимости рекомендовано Джо Даффи в его превосходной книге Параллельное программирование в Windows .

2 голосов
/ 26 сентября 2011

Если мы прочитаем устаревшее значение, то CompareExchange не выполнит обмен - мы в основном говорим: «Выполняйте операцию, только если значение действительно является тем, на котором мы основали наши вычисления». Пока в некоторой точке мы получаем правильное значение, это нормально. Было бы проблемой, если бы мы продолжали читать одно и то же устаревшее значение навсегда, поэтому CompareExchange никогда не прошел проверку, но я сильно подозреваю, что барьеры памяти CompareExchange означают, что по крайней мере по истечении времени, прошедшего через цикл, мы будем читать актуальное значение. Но самое худшее, что могло бы случиться, - это циклическая работа навсегда - важно то, что мы не можем обновить переменную неверным образом.

(И да, я думаю, вы правы, что свойство Total является хитрым.)

РЕДАКТИРОВАТЬ: Другими словами:

CompareExchange(ref totalValue, computedValue, initialValue)

означает: «Если текущее состояние действительно было initialValue, то мои вычисления верны, и вы должны установить его на computedValue».

Текущее состояние может быть неправильным по крайней мере по двум причинам:

  • В назначении initialValue = totalValue; использовалось устаревшее чтение с другим старым значением
  • Что-то изменилось totalValue после этого назначения

Нам вообще не нужно обрабатывать эти ситуации по-разному - поэтому хорошо делать «дешевое» чтение, пока в некотором пункте мы начнем видеть актуальные значения. ... и я полагаю, что барьеры памяти, задействованные в CompareExchange, гарантируют, что при циклическом цикле устаревшее значение, которое мы видим, будет таким же устаревшим, как и предыдущий CompareExchange вызов.

РЕДАКТИРОВАТЬ: Чтобы уточнить, я думаю, что образец правильный , если и только если CompareExchange составляет барьер памяти по отношению к totalValue. Если это не так - если мы все еще можем прочитать произвольно старые значения totalValue, когда мы продолжаем обходить цикл, - тогда код действительно нарушен и может никогда не завершиться.

0 голосов
/ 05 декабря 2018

Вопреки распространенному заблуждению, семантика получения / выпуска не гарантирует получение нового значения из общей памяти, они влияют только на порядок других операций с памятью вокруг операции с семантикой получения / выпуска. Каждый доступ к памяти должен быть, по крайней мере, таким же недавним, как и последнее чтение при получении, и не более устаревшим, чем запись следующего выпуска. (Аналогично для барьеров памяти.)

В этом коде у вас есть только одна общая переменная для беспокойства: totalValue. Тот факт, что CompareExchange является атомарной операцией RMW, достаточен для того, чтобы переменная, с которой он работает, обновлялась. Это связано с тем, что атомарные операции RMW должны гарантировать, что все процессоры согласуются с тем, что является самым последним значением переменной.

Относительно другого Total свойства, которое вы упомянули, будет ли оно правильным или нет, зависит от того, что от него требуется. Некоторые моменты:

* * 1010 int гарантированно является атомарным, поэтому вы всегда получите действительное значение (в этом смысле код, который вы показали, можно было бы считать «правильным», если бы ничего, кроме некоторого действительного) возможно, требуется устаревшее значение) если чтение без семантики получения (Volatile.Read или чтение volatile int) означает, что все операции с памятью, записанные после того, как это может действительно произойти раньше (операции чтения со старыми значениями и записи становятся видимыми для других процессоров, прежде чем они должны это сделать) если для чтения не используется атомарная операция RMW (например, Interlocked.CompareExchange(ref x, 0, 0)), полученное значение может не соответствовать тому, что некоторые другие процессоры видят как самое последнее значение если требуется как самое свежее значение, так и порядок в отношении других операций с памятью, Interlocked.CompareExchange должен работать (базовый WinAPI InterlockedCompareExchange использует полный барьер, не очень уверен в C # или .Net спецификации), но если вы хотите быть уверены, вы можете добавить явный барьер памяти после чтения
...