Блокировка требуется с ленивой инициализацией на очень неизменном типе?

Это конструктор, он вызывается при использовании new Startup()

public Startup(IConfiguration configuration)
{
    Configuration = configuration;
}

. Это автоматическое свойство, оно возвращает IConfigruation и автоматически реализует свойство Getter

public IConfiguration Configuration { get; }
.
10
задан Scott Whitlock 20 March 2009 в 00:55
поделиться

7 ответов

Это будет работать. Запись в ссылки в C#, как гарантируют, будет атомарной, как описано в разделе 5.5 из спецификации. Это - все еще, вероятно, не хороший способ сделать это, потому что Ваш код будет более сбивать с толку отладку и чтение в обмен на, вероятно, незначительный эффект на производительность.

У Jon Skeet есть большая страница при реализации одиночных элементов в C#.

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

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

5
ответ дан 3 December 2019 в 22:39
поделиться

Необходимо использовать блокировку. Иначе Вы рискуете двумя экземплярами m_PropName существующий и используемый различными потоками. Это не может быть проблемой во многих экземплярах; однако, если Вы хотите смочь использовать == вместо .equals() затем это будет проблемой. Редкие условия состязания не являются лучшей ошибкой, чтобы иметь. Их трудно отладить и воспроизвести.

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

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

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

5
ответ дан 3 December 2019 в 22:39
поделиться

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

Необходимо сделать поле volatile все же.

Относительно этого:

Однако я удивлен, что никто не поднял возможность компилятора, поняв, что временная переменная является ненужной, и просто присвоение прямо m_PropName. Если бы это имело место, то поток чтения мог возможно считать объект, который не закончил создаваться. Компилятор предотвращает такую ситуацию?

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

Однако язык/время выполнения действительно Едва ли гарантирует, что другие потоки не видят частично созданный объект - он зависит, что делает конструктор.

Обновление:

OP также задается вопросом, имеет ли эта страница полезную идею. Их заключительный фрагмент кода является экземпляром Проверенной дважды блокировки, которая является классическим примером идеи, что тысячи людей рекомендуют друг другу без любой идеи того, как сделать его правильно. Проблема состоит в том, что машины SMP состоят из нескольких центральных процессоров со своими собственными кэшами памяти. Если бы они должны были синхронизировать свои кэши каждый раз, когда было обновление памяти, это отменило бы преимущества наличия нескольких центральных процессоров. Таким образом, они только синхронизируются в "барьере памяти", который происходит, когда блокировка вынута, или взаимно блокируемая операция происходит, или a volatile к переменной получают доступ.

Обычный порядок событий:

  • Кодер обнаруживает перепроверяемую блокировку
  • Кодер обнаруживает барьеры памяти

Между этими двумя событиями они выпускают много поврежденного программного обеспечения.

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

Сводка: поточная обработка кодирует взгляды приблизительно 1 000 x легче записать, чем это.

5
ответ дан 3 December 2019 в 22:39
поделиться

Я не эксперт C#, но насколько я могу сказать, это только создает проблему, если Вы требуете, чтобы только один экземпляр ReadOnlyCollection был создан. Вы говорите, что созданный объект всегда будет тем же, и не имеет значения, если бы два (или больше) потоки действительно создают новый экземпляр, таким образом, я сказал бы, что нормально делать это без блокировки.

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

0
ответ дан 3 December 2019 в 22:39
поделиться

К сожалению, Вам нужна блокировка. Существует много довольно тонких ошибок, когда Вы не блокируете правильно. Поскольку пугающий пример смотрит на этот ответ.

0
ответ дан 3 December 2019 в 22:39
поделиться

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

Я думаю, что существует ключевое понятие, о котором забывают здесь: Согласно концепциям проекта C#, Вы не должны делать своих членов экземпляра ориентированными на многопотоковое исполнение по умолчанию. Только статические участники должны быть сделаны ориентированными на многопотоковое исполнение по умолчанию. Если Вы не получаете доступ к некоторым статическим/глобальным данным, Вы не должны добавлять дополнительные блокировки в свой код.

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

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

m_PropName = m_PropName ?? new ...();
return m_PropName;


Это избавляется от дополнительного "if (m_PropName == null) ..." и по-моему делает это более кратким и читаемым.

1
ответ дан 3 December 2019 в 22:39
поделиться

Это - определенно проблема.

Рассмотрите этот сценарий: Распараллельте "A" доступы свойство, и набор инициализируется. Прежде чем это присвоит локальный экземпляр полю "m_PropName", Поток "B" получает доступ к свойству, кроме него добирается для завершения. Распараллельте "B", теперь имеет ссылку на тот экземпляр, который в настоящее время хранится в "m_PropName"..., пока Поток "A" не продолжается, в котором точка "m_PropName" перезаписывается локальным экземпляром в том потоке.

Существует теперь несколько проблем. Во-первых, Поток "B" больше не имеет корректного экземпляра, так как объект владения думает, что "m_PropName" является единственным экземпляром, все же он просочился инициализированный экземпляр, когда Поток "B" завершился перед Потоком "A". Другой - то, если набор изменился между тем, когда Поток "A" и Поток "B" получили их экземпляры. Затем у Вас есть неправильные данные. Это могло даже быть хуже, если бы Вы наблюдали или изменяли набор только для чтения внутренне (который, конечно, Вы не можете с ReadOnlyCollection, но могли при замене его некоторой другой реализацией, которую Вы могли наблюдать через события или изменить внутренне, но не внешне).

-1
ответ дан 3 December 2019 в 22:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: