Почему неизменяемые объекты поточно-ориентированы? - PullRequest
31 голосов
/ 29 августа 2010
class Unit {
    private readonly string name;
    private readonly double scale;

    public Unit(string name, double scale) {
        this.name = name;
        this.scale = scale,
    }

    public string Name { get { return name; } }
    public string Scale { get { return scale; } }

    private static Unit gram = new Unit("Gram", 1.0);

    public Unit Gram { get { return gram; } }
}

Несколько потоков имеют доступ к Unit.Gram. Почему нормально читать несколько потоков одновременно Unit.Gram.Title?

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

Ответы [ 8 ]

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

Что делает объект не потокобезопасным? Объект не является потокобезопасным, если значение / состояние этого объекта может изменяться во время его чтения потоком. Обычно это происходит, если второй поток изменяет значение этого объекта, пока первый поток читает его.

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

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

Я думаю, что ваш вопрос не о поточной безопасности или неизменности, а о (очень) низком уровне детализации доступа к памяти.

И это здоровенная тема, но короткий ответ таков: Да, два потока (и, что более важно, 2+ ЦП) могут одновременно читать (и / или записывать) один и тот же фрагмент памяти.

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

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

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

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

Если объект неизменен, его состояние никогда не изменится.Поэтому проблемы устаревших данных уходят в окно.Чтение потоков никогда не блокируется, поэтому это не проблема (тупик)

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

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

Если для одновременного чтения требуется синхронизация, практически невозможно написать полезный многопоточный код. Например, чтобы выполнить фрагмент кода, процессор должен прочитать поток инструкций. Как бы вы написали функцию блокировки, если бы сама функция должна была защищать себя от одновременного выполнения:)?

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

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

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

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

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

В сети есть много примеров, но рассмотрим следующее: price является частным членом класса, скажем, Product, который также имеет 2 метода

public void setPrice(int value) {
  price = value;
  // -- point CRITIC --
  price += TAX;
}

public int getPrice() {
  return price;
}

setPrice (v) устанавливает цену продукта на v и корректирует ее с учетом НДС (программа должна иметь value += TAX; price = value, но здесь дело не в этом: -)

Если поток A записывает цену 100, а TAX равен (фиксированному) 1, цена продукта, наконец, будет установлена ​​равной 101. Но что произойдет, если поток B считывает цену через getPrice (), а поток A находится на point CRITIC? Цена, возвращаемая B, пропустит НАЛОГ и будет неверной.

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

    lock(this)
    {
      price = value;
      price += TAX;
    }
1 голос
/ 29 августа 2010

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

...