Ответ 1
Пусть здесь не похоронится: ответ на ваш вопрос - нет, вы никогда не заметите заранее выделенное состояние памяти в модели памяти CLR 2.0.
Теперь я обращусь к нескольким вашим нецентральным точкам.
Я понимаю, что С# является безопасным языком и не позволяет получить доступ к нераспределенной памяти, кроме как через небезопасное ключевое слово.
Это более или менее корректно. Есть некоторые механизмы, с помощью которых можно получить доступ к фальшивой памяти без использования unsafe
- например, через неуправляемый код или злоупотреблять макетом структуры. Но в целом, да, С# является безопасным для памяти.
Однако его модель памяти позволяет переупорядочивать, когда существует несинхронизированный доступ между потоками.
Опять же, это более или менее правильно. Лучший способ подумать о том, что С# позволяет переупорядочить в любой момент, когда переупорядочение будет невидимым для одной программы с резьбой, с учетом определенных ограничений. Эти ограничения включают введение семантики получения и выпуска в определенных случаях и сохранение определенных побочных эффектов в определенных критических точках.
Крис Брумме (из команды CLR)...
Постоянные великие статьи Криса - это драгоценные камни и дают представление о первых днях CLR, но я отмечаю, что с 2003 года были некоторые укрепления модели памяти, когда эта статья была написана, особенно в отношении проблемы, которую вы поднять.
Крис прав, что блокировка с двойной проверкой очень опасна. Существует правильный способ сделать двойную проверку блокировки на С#, и в тот момент, когда вы отходите от нее даже немного, вы находитесь в сорняках ужасных ошибок, которые воспроизводятся только на оборудовании модели с слабой памятью.
означает ли это, что другой поток может читать память, которая все еще содержит данные из собранных мусором объектов
Я думаю, ваш вопрос не в том, что касается старой модели памяти ECMA, которую описывал Крис, а скорее о том, какие гарантии фактически сделаны сегодня.
Невозможно, чтобы переупорядочения отображали предыдущее состояние объектов. Вам гарантировано, что когда вы читаете объект, выделенный недавно, его поля - это все нули.
Это стало возможным благодаря тому, что все записи имеют семантику выпуска в текущей модели памяти; см. это для деталей:
http://joeduffyblog.com/2007/11/10/clr-20-memory-model/
Запись, которая инициализирует память до нуля, не будет перемещаться вперед со временем по отношению к чтению позже.
Меня всегда путали "частично построенные объекты",
Джо обсуждает это здесь: http://joeduffyblog.com/2010/06/27/on-partiallyconstructed-objects/
Здесь беспокойство заключается не в том, что мы можем видеть состояние выделения объекта. Скорее, проблема здесь в том, что один поток может видеть объект, пока конструктор все еще работает в другом потоке.
В самом деле, возможно, что конструктор и финализатор будут работать одновременно, что является супер странным! По этой причине финализаторы трудно писать правильно.
Иными словами: CLR гарантирует вам, что его собственные инварианты будут сохранены. Инвариантом CLR является то, что вновь выделенная память считается обнуленной, так что инвариант будет сохранен.
Но CLR не занимается сохранением ваших инвариантов! Если у вас есть конструктор, который гарантирует, что поле x
true
тогда и только тогда, когда y
является нулевым, то вы несете ответственность за то, чтобы этот инвариант всегда считался истинным. Если каким-то образом this
наблюдается двумя потоками, то один из этих потоков может наблюдать нарушение инварианта.