.NET многопоточность, энергозависимая и модель памяти - PullRequest
4 голосов
/ 15 апреля 2010

Предположим, что у нас есть следующий код:

class Program
 {
    static volatile bool flag1;
    static volatile bool flag2;
    static volatile int val;
    static void Main(string[] args)
    {
      for (int i = 0; i < 10000 * 10000; i++)
      {
        if (i % 500000 == 0)
        {
          Console.WriteLine("{0:#,0}",i);
        }

        flag1 = false;
        flag2 = false;
        val = 0;

        Parallel.Invoke(A1, A2);

        if (val == 0)
          throw new Exception(string.Format("{0:#,0}: {1}, {2}", i, flag1, flag2));
      }
    }

    static void A1()
    {
      flag2 = true;
      if (flag1)
        val = 1;
    }
    static void A2()
    {
      flag1 = true;
      if (flag2)
        val = 2;
    }
  }
}

Это вина! Основной вопрос: «Почему ... Я предполагаю, что процессоры переупорядочивают операции с flag1 = true; и оператор if (flag2), но переменные flag1 и flag2 помечены как изменяемые поля ...

Ответы [ 2 ]

4 голосов
/ 15 апреля 2010

В модели памяти .NET среда выполнения (CLI) гарантирует, что изменения в энергозависимых полях не будут кэшироваться в регистрах, поэтому изменения в любом потоке сразу же видны в других потоках ( NB , это не верно для других моделей памяти, включая Java).

Но это ничего не говорит об относительном порядке операций в нескольких, изменчивых или нет полях.

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

Подробнее см. «Параллельное программирование в Windows», Джо Даффи, AW, 2008

0 голосов
/ 04 августа 2017

ECMA-335 спецификация гласит:

Летучее чтение имеет «приобретенную семантику» , означающее, что чтение гарантированно произойдет до любых ссылок на память, которые происходят после инструкции чтения в последовательности команд CIL. A volatile запись имеет «семантику релиза» , означающую, что запись гарантированно произойдет после любых ссылок на память до записи инструкция в последовательности команд CIL. Соответствующий реализация CLI должна гарантировать эту семантику изменчивых операции. Это гарантирует, что все потоки будут наблюдать изменчивость записи выполняются любым другим потоком в порядке их выполнения. Но соответствующая реализация не требуется предоставлять единый общий порядок изменчивых записей как видно из всех потоков исполнения.

Давайте нарисуем, как это выглядит:

enter image description here

Итак, у нас есть два полусферы: одна для энергозависимой записи, а другая для энергозависимого чтения. И они не защищают нас от переупорядочения инструкций между ними.
Более того, даже на такой строгой архитектуре, как AMD64 (x86-64) , разрешается переупорядочивать магазины после загрузки .
А для других архитектур с более слабой аппаратной моделью памяти вы можете наблюдать еще более забавные вещи. На ARM вы можете получить частично построенный объект наблюдения, если ссылка была назначена энергонезависимым способом.

Чтобы исправить ваш пример, вы должны просто сделать Thread.MemoryBarrier() вызовы между присваиванием и условием if:

static void A1()
{
  flag2 = true;
  Thread.MemoryBarrier();
  if (flag1)
    val = 1;
}
static void A2()
{
  flag1 = true;
  Thread.MemoryBarrier();
  if (flag2)
    val = 2;
}

Это защитит нас от переупорядочения этих инструкций, добавив полный забор.

...