Гарантирует ли Interlocked видимость для других потоков в C # или мне все еще нужно использовать volatile?

Я читал ответ на похожий вопрос, но я все еще немного запутался ... У Абеля был отличный ответ, но я не уверен в этом:

... объявление переменной volatile делает ее изменчивой для каждого отдельного доступа. Невозможно заставить это поведение каким-либо другим способом, следовательно, volatile нельзя заменить на Interlocked. Это необходимо в сценариях, когда другие библиотеки, интерфейсы или оборудование могут получить доступ к вашей переменной и обновить ее в любое время, или если вам нужна самая последняя версия.

Гарантирует ли Interlocked видимость атомарной операции для всех потоков, или мне все же нужно использовать ключевое слово volatile для значения, чтобы гарантировать видимость изменения?

Вот мой пример:

volatile int value = 100000; // <-- do I need the volitile keyword
// ....

public void AnotherThreadMethod()
{
 while(Interlocked.Decrement(ref value)>0)
 {
  // do something
 }
}


public void AThreadMethod()
{
 while(value > 0)
 {
  // do something
 }
}

Обновление:
Я был плохим спортсменом, и я изменил исходный пример, так что вот он снова:

public class CountDownLatch
{
    private volatile int m_remain; // <--- do I need the volatile keyword here?
    private EventWaitHandle m_event;

    public CountDownLatch(int count)
    {
        Reset(count);
    }

    public void Reset(int count)
    {
        if (count < 0)
            throw new ArgumentOutOfRangeException();
        m_remain = count;
        m_event = new ManualResetEvent(false);
        if (m_remain == 0)
        {
            m_event.Set();
        }
    }

    public void Signal()
    {
        // The last thread to signal also sets the event.
        if (Interlocked.Decrement(ref m_remain) == 0)
            m_event.Set();
    }

    public void Wait()
    {
        m_event.WaitOne();
    }
}

person Kiril    schedule 19.03.2010    source источник
comment
Извините за многочисленные правки ... Ремус очень любит это. Спасибо за информацию! :)   -  person Kiril    schedule 19.03.2010


Ответы (2)


Им не нужна * изменчивость, потому что вы никогда не проверяете значение заблокированной переменной. Вместо этого вы всегда проверяете значение , возвращаемое блокированными операциями. Смешивание взаимосвязанных операций и обычного присваивания / сравнения всегда приводит к неправильному коду.

Я не уверен, что такое функция Reset (), но этому фрагменту кода нет места в межпотоковом примитиве: вы присваиваете m_remain, вы напрямую проверяете значение m_remain, это довольно плохо. Я настоятельно рекомендую вам убрать это: не только реализовано неправильно, но я очень сомневаюсь, что семантика «сброса» счетчика в середине жизненного цикла необходима. Оставьте это просто: ctor (переместите в него код из Reset) Signal и Wait - единственные необходимые три оператора, и они верны, как и сейчас.

Обновлено после того, как вы отредактировали код.

Игнорируя тот факт, что вы не должны смешивать эти два, если вы в конечном итоге смешаете их, тогда да, volatile все еще необходим. Volatile в первую очередь касается кода IL и JIT-кода, сгенерированного, чтобы гарантировать, что значение всегда считывается из фактического места в памяти и не происходит оптимизации, такой как переупорядочение кода. Тот факт, что несвязанный фрагмент кода обновляет значение с помощью взаимосвязанных операций, не влияет на другие части, считывающие значение. Без атрибута volatile компилятор / JIT может по-прежнему генерировать код, который игнорирует записи, которые происходят где-то еще, не имеет значения, если записи заблокированы или прямое присвоение.

Кстати, существуют допустимые шаблоны, которые смешивают обычные операции чтения и заблокированные операции, но они обычно включают Interlocked.CompareExchange и так далее: читать текущее состояние, выполнять некоторые вычисления на основе текущего состояния, пытаться заменить состояние как заблокированное сравнение-обмен: в случае успеха - нормально, если нет, отбросьте результат вычислений и вернитесь к шагу 1.

person Remus Rusanu    schedule 19.03.2010
comment
Извините, я привел пример ... Я действительно не хотел вас обманывать! :) - person Kiril; 19.03.2010
comment
Итак, уходит функция сброса ... это действительно не имело никакого смысла. Я подумал, что это может быть полезно для повторного использования той же защелки, но это добавляет больше путаницы, чем что-либо еще. - person Kiril; 19.03.2010
comment
Я думал, что цель Reset. Было бы прекрасно, если бы вы точно знали, что вызывается только в безопасном контексте, но вы никогда не узнаете. Намного проще предоставить API, который разработчики не могут использовать неправильно, чем предоставить API, которым можно злоупотреблять, и попросить пользователей быть осторожными. Никто не читает мануал ... - person Remus Rusanu; 19.03.2010
comment
Есть ли что-то неправильное с переменной шаблона для обычного чтения; вычислить скорректированное значение; Переменная Interlock.CompareExchange на новое значение, если оно равно старому значению; повторить все, если CompareExchange не удалось? Если исходное чтение устарело, цикл будет выполняться дополнительное время, если какой-либо внешний объект не изменит запись значения, которое соответствует одному чтению, но в общем случае для этого потребуется только одна заблокированная операция, а не две. - person supercat; 04.03.2012

Я думаю, что System.Threading.Thread.VolatileRead (ref myVariable) может быть тем, что вы ищете. Используется вместе с Interlocked.Increment, его можно использовать, чтобы гарантировать, что изменения являются атомарными, а значения, которые вы читаете, являются самыми последними.

person open-collar    schedule 23.03.2010