Подтвердить что ты не робот

Возможность переменной свежести в .NET(волатильная или неустойчивая запись)

Я прочитал много противоречащей информации (msdn, SO и т.д.) об изменчивости и VoletileRead (ReadAcquireFence).

Я понимаю, что ограничение доступа к реорганизации доступа к памяти - это то, что я до сих пор полностью смущен, - это гарантия свежести, что очень важно для меня.

msdn doc для volatile упоминает:

(...) Это гарантирует, что самое актуальное значение всегда присутствует в поле.

msdn doc для изменчивых полей упоминает:

Чтение изменчивого поля называется изменчивым чтением. Неустойчивое чтение имеет "приобретать семантику"; то есть он гарантированно будет происходить до любых ссылок на память, которые происходят после него в последовательности команд.

. NET-код для VolatileRead:

public static int VolatileRead(ref int address)
{
    int ret = address;
    MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way.
    return ret;
}

В соответствии с msdn MemoryBarrier doc Предел памяти препятствует переупорядочению. Однако, похоже, это не влияет на свежесть - правильно?

Как тогда можно получить гарантию свежести? И разве разница между полем маркировки изменчивым и доступ к нему с помощью семантики VolatileRead и VolatileWrite? В настоящее время я делаю последнее в своем критическом критическом коде, которое должно гарантировать свежесть, однако читатели иногда получают устаревшее значение. Мне интересно, изменит ли положение состояние волатильность ситуации.

EDIT1:

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

Если эта энергозависимая или более строгая конструкция (например, блокировка) имеет эту гарантию (они?), чем они достигают этого?

EDIT2:

Очень сжатый вопрос должен был быть как мне получить гарантию как свежую ценность при чтении как можно? В идеале без блокировки (поскольку эксклюзивный доступ не нужен, и есть вероятность высокой конкуренции).

Из того, что я узнал здесь, мне интересно, может ли это быть решением (строка решения (?) помечена комментарием):

private SharedState _sharedState;
private SpinLock _spinLock = new SpinLock(false);

public void Update(SharedState newValue)
{
    bool lockTaken = false;
    _spinLock.Enter(ref lockTaken);

    _sharedState = newValue;

    if (lockTaken)
    {
        _spinLock.Exit();
    }
}

public SharedState GetFreshSharedState
{
    get
    {
        Thread.MemoryBarrier(); // <---- This is added to give readers freshness guarantee
        var value = _sharedState;
        Thread.MemoryBarrier();
        return value;
    }
}

Был добавлен вызов MemoryBarrier, чтобы убедиться, что оба - чтение и запись - завернуты полными заборами (такими же, как код блокировки - как указано здесь http://www.albahari.com/threading/part4.aspx#_The_volatile_keyword "Разделение памяти и блокировка" )

Означает ли это, что это правильно или оно испорчено?

EDIT3:

Благодаря очень интересным дискуссиям здесь я узнал немало вещей, и я действительно смог перейти к упрощенному недвусмысленному вопросу, который у меня есть об этой теме. Это сильно отличается от оригинального, поэтому я скорее разместил здесь новый: Защитный барьер против блокированного воздействия на таймер синхронизации кэшей памяти

4b9b3361

Ответ 1

Я думаю, что это хороший вопрос. Но на это также трудно ответить. Я не уверен, что могу дать вам окончательный ответ на ваши вопросы. Это не ваша вина. Дело в том, что предмет сложный и требует знания сведений, которые невозможно было бы перечислить. Честно говоря, на самом деле кажется, что вы уже хорошо обучили себя этому вопросу. Я потратил много времени на изучение предмета, и я все еще не полностью понимаю все. Тем не менее, я все равно попытаюсь найти какое-то подобие ответа здесь.

Итак, что означает, что поток все равно читает новое значение? Означает ли это, что значение, возвращаемое чтением, не должно превышать 100 мс, 50 ​​мс или 1 мс? Или это означает, что значение является абсолютным последним? Или это означает, что если два чтения поступают обратным образом, то второй гарантированно получит более новое значение, предполагая, что адрес памяти изменился после первого чтения? Или это вообще означает что-то еще?

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

public static T InterlockedOperation<T>(ref T location, T operand)
{
  T initial, computed;
  do
  {
    initial = location;
    computed = op(initial, operand); // where op is replaced with a specific implementation
  } 
  while (Interlocked.CompareExchange(ref location, computed, initial) != initial);
  return computed;
}

В приведенном выше коде мы можем создать любую связанную с блокировкой операцию, если мы воспользуемся тем фактом, что второе чтение location через Interlocked.CompareExchange будет гарантировано возвращать более новое значение, если адрес памяти получил запись после первого читать. Это связано с тем, что метод Interlocked.CompareExchange создает барьер памяти. Если значение изменилось между чтением, тогда код циклически вращается вокруг цикла, пока location перестанет меняться. Этот шаблон не требует, чтобы код использовал последнее или самое свежее значение; просто более новое значение. Различие имеет решающее значение. 1

Множество незаблокированных кодов, которые я видел, работает над этим руководителем. Это операции, как правило, завернуты в циклы, так что операция постоянно повторяется до тех пор, пока она не удастся. Он не предполагает, что первая попытка использует последнее значение. Он также не предполагает, что каждое использование значения будет самым последним. Он предполагает, что после каждого чтения значение становится более новым.

Попробуйте переосмыслить поведение своих читателей. Постарайтесь сделать их более агностиками в отношении возраста стоимости. Если это просто невозможно, и все записи должны быть захвачены и обработаны, тогда вы можете быть принуждены к более детерминированному подходу, например, помещать все записи в очередь и иметь возможность считывать их по очереди один за другим. Я уверен, что класс ConcurrentQueue поможет в этой ситуации.

Если вы можете уменьшить значение "свежего" только на "новее", а затем поместить вызов Thread.MemoryBarrier после каждого чтения, используя Volatile.Read, используя ключевое слово volatile и т.д., абсолютно гарантирует, что прочитайте в последовательности вернет более новое значение, чем предыдущее чтение.


1 Проблема

Ответ 2

Защитный барьер обеспечивает эту гарантию. Мы можем получить свойство "свежести", которое вы ищете, от свойств записи, которые гарантируют барьер.

По свежести вы, вероятно, имеете в виду, что чтение возвращает значение самой последней записи.

Предположим, что мы имеем эти операции, каждый из которых имеет другой поток:

x = 1
x = 2
print(x)

Как мы можем напечатать значение, отличное от 2? Без volatile чтение может перемещать один слот вверх и возвращать 1. Volatile предотвращает переупорядочивания. Запись не может двигаться назад во времени.

Короче говоря, volatile гарантирует вам самое последнее значение.

Строго говоря, мне нужно будет различать волатильность и барьер памяти здесь. Последний является более надежной гарантией. Я упростил это обсуждение, потому что volatile реализован с использованием барьеров памяти, по крайней мере, на x86/x64.