Летучий и нитевидный.Перед вызовом memorybarrier в C#

для реализации блокировка бесплатный код для многопоточного приложения я использовал volatile переменные, теоретически: элемент volatile ключевое слово просто используется, чтобы убедиться, что все потоки видят самое обновленное значение изменчивой переменной; поэтому, если поток A обновляет значение переменной и нити B прочитайте эту переменную сразу после того, как это обновление произойдет, он увидит самое обновленное значение, написанное недавно из потока A. Как я прочитал в в C# 4.0 в в двух словах!--17--> книга это неправильно, потому что

применение volatile не предотвращает замену записи с последующим чтением.

может ли эта проблема быть решена путем сдачи Thread.MemoryBarrier() перед каждым на volatile переменной типа:

private volatile bool _foo = false;

private void A()
{
    //…
    Thread.MemoryBarrier();
    if (_foo)
    {
        //do somthing
    }
}

private void B()
{
    //…
    _foo = true;
    //…
}

и если это решает проблему; рассмотрим, что у нас есть цикл while, который зависит от этого значения при одном из его условий; ставит Thread.MemoryBarrier() перед while loop-это правильный способ исправить проблему? пример:

private void A()
{
    Thread.MemoryBarrier();
    while (_someOtherConditions && _foo)
    {
        // do somthing.
    }
}

чтобы быть более точным, я хочу, чтобы _foo переменная, чтобы дать свое самое свежее значение, когда любой поток просит его в любое время; поэтому, если вставить Thread.MemoryBarrier() перед вызовом переменной устраняет проблему, тогда я мог бы использовать Foo собственность вместо _foo и Thread.MemoryBarrier() в пределах получения этого свойства, как:

Foo
{
    get 
    {
        Thread.MemoryBarrier();
        return _foo;
    }
    set
    {
        _foo = value;
    }
}

5 ответов


"C# в двух словах" является правильным, но его утверждение спорно. Почему?

  • "write", за которым следует "read", без "volatile", гарантированно происходит в программном порядке в любом случае если это влияет на логику в пределах одного потока
  • "write" перед "read" в многопоточной программе -совершенно бессмысленно беспокоиться о в вашем примере.

давайте уточним. Возьмите оригинал код:

private void A() 
{ 
    //… 
    if (_foo) 
    { 
        //do something 
    } 
}

что произойдет, если планировщик потоков уже проверил _foo переменная, но она приостанавливается непосредственно перед //do something комментарии? Ну, в этот момент другой поток может изменить значение _foo, что означает, что все ваши летучие вещества и нити.MemoryBarriers рассчитывали зря!!! Если это абсолютно необходимо, чтобы do_something следует избегать, если значение _foo false, тогда у вас нет выбора, кроме как использовать замок.

однако, если это это нормально для do something для выполнения, когда внезапно _foo становится false, то это означает, что ключевое слово volatile было более чем достаточно для ваших нужд.

чтобы быть ясным: все ответчики, которые говорят вам использовать барьер памяти, неверны или предоставляют излишек.


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

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

С помощью volatile ключевое слово само по себе не создает критических разделов, и это не заставляет потоки волшебным образом синхронизироваться друг с другом.

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


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


вытащил из здесь...

class Foo
{
  int _answer;
  bool _complete;

  void A()
  {
    _answer = 123;
    Thread.MemoryBarrier();    // Barrier 1
    _complete = true;
    Thread.MemoryBarrier();    // Barrier 2
  }

  void B()
  {
    Thread.MemoryBarrier();    // Barrier 3
    if (_complete)
    {
      Thread.MemoryBarrier();       // Barrier 4
      Console.WriteLine (_answer);
    }
  }
}

барьеры 1 и 4 предотвращают этот пример от написания "0". Барьеры 2 и 3 обеспечьте гарантию свежести: они убедитесь, что если B побежал после A, чтение _complete будет иметь значение true.

Итак, если мы вернемся к вашему примеру цикла...вот как это должно выглядеть...

private void A()
{
    Thread.MemoryBarrier();
    while (_someOtherConditions && _foo)
    {
        //do somthing
        Thread.MemoryBarrier();
    }
}

собственные слова Microsoft о барьерах памяти:

MemoryBarrier требуется только в многопроцессорных системах со слабым порядком памяти (например, в системе, использующей несколько процессоров Intel Itanium).

для большинства целей оператор блокировки C#, оператор SyncLock Visual Basic или класс Monitor предоставляют более простые способы синхронизации данных.