Модель памяти C # и энергонезависимая переменная, инициализированные до создания другого потока - PullRequest
6 голосов
/ 29 августа 2010

У меня есть вопрос, связанный с моделью памяти C # и потоками. Я не уверен, что следующий код правильный без ключевого слова volatile .

public class A {
  private int variableA = 0;

  public A() {

    variableA = 1;

    Thread B = new Thread(new ThreadStart(() => printA())).Start();
  }

  private void printA() {
    System.Console.WriteLine(variableA);
  }
}

Меня беспокоит, будет ли гарантировано, что поток B увидит переменную A со значением 1 без использования volatile ? В основном потоке я только назначаю 1 переменной A в конструкторе. После этого я не касаюсь переменной A, она используется только в потоке B, поэтому блокировка, вероятно, не требуется.

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

Кроме того, гарантируется ли, что второй поток будет считывать содержимое переменной A из основной памяти? Могут ли произойти некоторые оптимизации компилятора, и Поток B может читать содержимое переменной A из кэша вместо основной памяти? Это может произойти при изменении порядка инструкций.

Конечно, добавление volatile к объявлению variableA сделает код правильным. Но это необходимо? Я спрашиваю, потому что я написал некоторый код с инициализацией некоторых энергонезависимых переменных в конструкторе, и переменные позже используются некоторыми потоками Timer, и я не уверен, что это полностью правильно.

А как насчет того же кода в Java?

Спасибо, Михал

Ответы [ 3 ]

5 голосов
/ 30 августа 2010

Есть много мест, где создаются неявные барьеры памяти.Это одна из них.Начальные темы создают полные барьеры.Таким образом, запись в variableA будет зафиксирована до начала потока, и первые чтения будут получены из основной памяти.Конечно, в реализации Microsoft CLR это спорный вопрос, потому что записи уже имеют изменчивую семантику.Но такая же гарантия не предусмотрена в спецификации ECMA, поэтому теоретически возможно, что реализация Mono может вести себя по-другому в этом отношении.

Меня беспокоит, гарантируется ли, что поток B увидит переменную Aсо значением 1 без использования volatile?

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

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

Да.

Дополнительно гарантируется, что второй потокбудет читать содержимое переменной A из основной памяти?

Да, но только при первом чтении.

Конечно, добавление volatile в объявление переменной A сделает кодправильный.Но разве это необходимо?

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

4 голосов
/ 29 августа 2010

Но гарантируется ли, что основной поток очистит свой кэш и запишет содержимое переменной A в основную память,

Да, это гарантируется моделью памяти MS CLR.Не обязательно так для других реализаций CLI (то есть я не уверен насчет Mono).Стандарт ECMA этого не требует.

, чтобы второй поток мог прочитать вновь присвоенное значение?

Для этого необходимо обновить кэш.Вероятно, это гарантировано созданием Нити (как сказал Джон Скит).Это, однако, не гарантируется предыдущим пунктом.Кэш сбрасывается при каждой записи, но не при каждом чтении.

Вы можете убедиться в этом, используя VolatileRead(ref variableA), но рекомендуется (Джеффри Рихтер) использовать класс Interlocked.Обратите внимание, что VolatileWrite() является лишним в MS.NET.

4 голосов
/ 29 августа 2010

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

Однако я не знаю, что гарантировано в .NET в отношении создания нового потока.Еще большее беспокойство вызывает возможность отложенного чтения при использовании Control.BeginInvoke и т. П. ... Я не видел никаких гарантий в отношении барьеров памяти в таких ситуациях. все хорошо.Я подозреваю, что все, что должно координироваться между потоками, подобными этому (либо создание нового, либо маршалинг вызова на существующий), будет использовать полный барьер памяти для обоих задействованных потоков.Однако вы абсолютно правы, и я надеюсь, что вы получите более точный ответ от кого-то умнее меня.Возможно, вы захотите написать Джо Даффи по электронной почте, чтобы узнать его мнение об этом ...

...