Безопасно ли заменять значение переменной-члена потоком? - PullRequest
9 голосов
/ 23 марта 2012

В моем приложении (написанном на C #) у меня есть экземпляр класса с переменной-членом, которая указывает на экземпляр другого класса. Этот второй экземпляр доступен только для чтения, поэтому его состояние никогда не изменится после его создания. Но в некоторых случаях я хочу заменить этот экземпляр на новый и обновленный экземпляр. Безопасна ли замена этой ссылки в первую очередь в многопоточной среде? Или это может вызвать проблемы?

Ответы [ 6 ]

9 голосов
/ 23 марта 2012

Простая операция записи сама по себе является поточно-ориентированной.

Так что все в порядке:

x.y = new Y();   // OK

, но это не так:

if (x.y == null)
    x.y = new Y();   // might overwrite a non-null x.y

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

6 голосов
/ 23 марта 2012

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

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

3 голосов
/ 23 марта 2012

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

Извините, что wishy-washy здесь, но это зависит от того, как вы определяете"safe".

  • Это безопасно, поскольку не повредит состояние.Это связано с тем, что эталонные назначения являются атомарными.
  • Это не безопасно в том смысле, что это может привести к непреднамеренному поведению.

Непреднамеренное поведение можно разделить на триширокие категории.

  • Комбинация чтения с последующей записью может привести к состоянию гонки.
  • Один поток может не наблюдать изменения, внесенные другим из-за оптимизации компилятора и аппаратного обеспечения.
  • В одном потоке может наблюдаться изменение между двумя различными чтениями ссылочной переменной.

Случай № 1: Таким образом, в первом случае два потока могут переходить ксделать назначение под ложными предлогами.Лучший пример этого - создание синглтона.

public static Singleton GetSingleton()
{
  // More than one thread could read null and attempt instantiation here.
  if (instance == null)
  {
    instance = new Singleton();
  }
  return instance;
} 

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

public class Example
{
  private SuperHero hero = new Superman();

  void ThreadA()
  {
    while (true)
    {
      Thread.Sleep(1000);
      hero = new CaptainAmerica();
      Thread.Sleep(1000);
      hero = new GreenLantern();
      Thread.Sleep(1000);
      hero = new IronMan();
    }

  void ThreadB()
  {
    while (true)
    {
      hero.FightTheBadGuys();
    }
  }
}

Проблема в том, что компилятор или аппаратное обеспечение может оптимизировать ThreadB, "подняв" чтение значения hero вне цикла, как это.

void ThreadB()
{
  SuperHero local = hero;
  while (true)
  {
    local.FightTheBadGuys();
  }
}

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

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

void SomeThread()
{
  var result1 = instance.DoSomething();
  // The variable instance could get changed here!
  var result2 = instance.DoSomethingElse();
  // Are result1 and result2 in a mutually consistent state here?
}

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

3 голосов
/ 23 марта 2012

Замена ссылки - это 32-битная целочисленная операция записи в 32-битной среде и 64-битная целочисленная запись в 64-битной среде соответственно. И операция записи действительно выполняется в два этапа:

  • Считать новое значение
  • Введите новое значение

Я считаю, что по умолчанию он не безопасен для потоков

1 голос
/ 23 марта 2012

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

  • Объявите это поле как volatile.
  • Используйте Interlocked.Exchange() для замены значения.

Прочтите документацию MSDNвыберите, что лучше для вас (быстро: изменчиво должно быть быстрее, но вы не можете сохранить старое значение и записать новое за одну атомарную операцию).

РЕДАКТИРОВАНИЕ Я думаю, что я должен уточнить, что я имею в виду (многие люди считают атомарный синонимом поточно-безопасным ):

Заменяет эту ссылкув первую очередь безопасно в многопоточной среде?

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

Или это может вызвать проблемы?

Это зависит от контекста.Если один поток не будет использовать правильное сокетное соединение (или закрытое), то это будет большой проблемой.Если один поток не будет использовать правильный цвет для форматирования своего вывода, тогда это может не быть проблемой.

0 голосов
/ 30 апреля 2012

Используйте следующий код, где вы хотите обновить объект новым экземпляром. это потокобезопасный код.

MyClass obj = null;
Interlocked.CompareExchange(ref obj, new MyClass(), null);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...