Я часто слышал, что в модели памяти .NET 2.0 записи всегда используют
освободить заборы. Это правда?
Это зависит от того, на какую модель вы ссылаетесь.
Во-первых, давайте точно определим барьер для ограждения. Семантика освобождения предусматривает, что никакое другое чтение или запись, появляющиеся перед барьером в последовательности команд, не могут двигаться после этого барьера.
- Спецификация ECMA имеет упрощенную модель, в которой записи не предоставляют эту гарантию.
- Где-то упоминалось, что реализация CLR, предоставленная Microsoft, усиливает модель, делая записи семантикой ограничения выпуска.
- Архитектуры x86 и x64 усиливают модель, записывая барьеры освобождения и ограждения и читая барьеры захвата и ограждения.
Таким образом, возможно, что другая реализация CLI (например, Mono), работающая на эзотерической архитектуре (например, ARM, на которую теперь будет ориентироваться Windows 8), будет , а не , обеспечивать семантику ограничения выпуска при записи. Обратите внимание, что я сказал, что это возможно, но не уверен. Но между всеми играющими моделями памяти, такими как различные программные и аппаратные уровни, вы должны написать код для самой слабой модели, если вы хотите, чтобы ваш код был действительно переносимым. Это означает кодирование по модели ECMA и не делать никаких предположений.
Мы должны сделать список слоев модели памяти в игре просто явным.
- Компилятор: C # (или VB.NET или любой другой) может перемещать инструкции.
- Время выполнения: Очевидно, что время выполнения CLI через JIT-компилятор может перемещать инструкции.
- Аппаратное обеспечение: И, конечно, архитектура процессора и памяти также играет роль.
Значит ли это, что даже без явных барьеров или блокировок памяти
невозможно наблюдать частично построенный объект (учитывая
только ссылочные типы) в потоке, отличном от того, в котором он
создан?
Да (квалифицировано): Если среда, в которой запущено приложение, достаточно неясна, возможно, что частично построенный экземпляр будет наблюдаться из другого потока. Это одна из причин, по которой двойная проверка шаблона блокировки была бы небезопасной без использования volatile
. В действительности, однако, я сомневаюсь, что вы когда-нибудь столкнетесь с этим главным образом потому, что реализация CLI от Microsoft не будет переупорядочивать инструкции таким образом.
Можно ли с помощью следующего кода наблюдать любой вывод
кроме «Джон 20» и «Джек 21», скажем «ноль 20» или «Джек 0»?
Опять же, это квалифицировано, да. Но по какой-то причине, как указано выше, я сомневаюсь, что вы когда-нибудь будете наблюдать такое поведение.
Тем не менее, я должен отметить, что, поскольку person
не помечен как volatile
, возможно, что вообще ничего не печатается, потому что поток чтения всегда может видеть его как null
. В действительности, однако, я уверен, что вызов Console.WriteLine
заставит компиляторы C # и JIT избежать операции подъема, которая в противном случае могла бы вывести чтение person
за пределы цикла. Я подозреваю, что вы уже хорошо знаете этот нюанс.
Означает ли это, что я могу просто сделать все общие поля
глубоко неизменные ссылочные типы volatile и (в большинстве случаев)
с моей работой?
Я не знаю. Это довольно загруженный вопрос. Мне неудобно отвечать в любом случае без лучшего понимания контекста. Что я могу сказать, так это то, что я обычно избегаю использования volatile
в пользу более явных инструкций памяти, таких как Interlocked
операции, Thread.VolatileRead
, Thread.VolatileWrite
и Thread.MemoryBarrier
. Опять же, я также стараюсь вообще избегать кода без блокировки в пользу механизмов синхронизации более высокого уровня, таких как lock
.
Обновление:
Один из способов, которым я люблю визуализировать вещи, - это предположить, что компилятор C #, JITer и т. Д. Будут оптимизированы настолько агрессивно, насколько это возможно.Это означает, что Person.ctor
может быть кандидатом на встраивание (поскольку оно простое), что приведет к следующему псевдокоду.
Person ref = allocate space for Person
ref.Name = name;
ref.Age = age;
person = instance;
DoSomething(person);
И поскольку записи не имеют семантики ограничения выпуска в спецификации ECMA, тогда как другиеоперации чтения и записи могут «плавать» за присвоением person
, приводя к следующей действительной последовательности инструкций.
Person ref = allocate space for Person
person = ref;
person.Name = name;
person.Age = age;
DoSomething(person);
Таким образом, в этом случае вы можете видеть, что person
назначается до его инициализации.Это верно, потому что с точки зрения исполняющего потока логическая последовательность остается согласованной с физической последовательностью.Там нет непреднамеренных побочных эффектов.Но по причинам, которые должны быть очевидны, эта последовательность была бы катастрофической для другого потока.