2016-12-06 13 views
6

Этот вопрос является комментарием в комментарии this темы.Может ли инструкция чтения после того, как несвязанный оператор блокировки будет перемещен перед блокировкой?

Давайте предположим, что мы имеем следующий код:

// (1) 
lock (padlock) 
{ 
    // (2) 
} 
var value = nonVolatileField; // (3) 

Кроме того, давайте предположим, что никакой инструкции в (2) не имеет никакого влияния на nonVolatileField и наоборот.

Может ли инструкция считывания (3) быть переупорядочена таким образом, чтобы она заканчивалась перед оператором блокировки (1) или внутри нее (2)?

Насколько я могу судить, ничто в Спецификации C# (§3.10) и Спецификации CLI (§I.12.6.5) не запрещает такое переупорядочение.

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

ответ

3

I считаю, что частично гарантируется спецификацией CLI, хотя это не так ясно, как могло бы быть. От I.12.6.5:

Получения блокировки (System.Threading.Monitor.Enter или ввод синхронизированного метода) должен неявно выполнить летучую операцию чтения и снятие блокировки (System.Threading.Monitor.Exit или выход из него синхронизированного метода) должен неявно выполнить летучее запись. См. §I.12.6.7.

Тогда из I.12.6.7:

Летучие на чтение «приобретает семантику» означает, что чтение гарантированно произойдет до каких-либо ссылок на память, которые происходят после команды чтения в Последовательность команд CIL. У volatile write есть «семантика релиза», что означает, что запись гарантирована после каждой записи памяти до команды записи в последовательности команд CIL.

Поэтому вход в замок должен препятствовать перемещению (3) к (1). Чтение с nonVolatileField по-прежнему считается «ссылкой на память», я считаю. Тем не менее, чтение все равно может быть выполнено до волатильной записи, когда блокировка завершена, поэтому ее все равно можно переместить в (2).

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

+0

Hi Jon, извините, что уволил эту ветку комментариев, но это похоже на хорошее место для моего вопроса, есть ли где-то спецификационный документ C# 6 или 7? Я знаю, что Mads Torgersen упоминал где-то, что у него есть проект для C# 6, который он должен опубликовать, но я никогда не видел, если бы он это сделал. Я чувствую, что, если кто-нибудь знает о таком документе, это будет вам :) –

+0

@Damien_The_Unbeliever: Вы правы, будете редактировать. –

+0

@ LasseV.Karlsen: есть черновик спецификации C# 6 на https://github.com/ljw1004/csharpspec, но он * - это просто черновик. Мы по-прежнему заканчиваем стандартизацию спецификации C# 5 для ECMA, которая имеет эффект постукивания для более поздних версий, поскольку мы надеемся согласовать ECMA и MS. –

2

Что касается .NET, то вход монитора (оператор lock) имеет семантику, поскольку он неявно выполняет изменчивое чтение и выход из монитора (конец блока lock) имеет семантику выпуска, так как она неявно выполняет волатильную запись (см. §12.6.5 Замки и потоки в Common Language Infrastructure (CLI) Partition I).

volatile bool areWeThereYet = false; 

// In thread 1 
// Accesses, usually writes: create objects, initialize them 
areWeThereYet = true; 

// In thread 2 
if (areWeThereYet) 
{ 
    // Accesses, usually reads: use created and initialized objects 
} 

При записи значения areWeThereYet, все доступы до того, как были выполнены и не заказана, чтобы после летучего записи.

Когда вы читаете с areWeThereYet, последующие обращения не переупорядочиваются до более неустойчивого чтения.

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

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


Многие люди разделяют мысль, что Microsoft должна обеспечивать сильную модель памяти на таких архитектурах, подобных x86/amd64, чтобы сохранить текущий код базы (Microsoft, собственных, так и их клиентов) совместимы.

я не могу проверить себя, так как я не имею ARM устройства с Microsoft Windows, намного меньше с .NET Framework для ARM, но, по крайней мере, один статьи в журнале MSDN Эндрю Пардо, CLR - .NET Development for ARM Processors, говорится:

CLR разрешает выставлять более сильную модель памяти, чем требуется спецификация CLI ECMA. Например, на x86 модель памяти CLR сильна, потому что модель памяти процессора сильная. Команда .NET могла бы сделать модель памяти на ARM такой же сильной, как модель на x86, но обеспечение идеального порядка, когда это возможно, может оказать заметное влияние на производительность выполнения кода. Мы провели целенаправленную работу по усилению модели памяти на ARM - в частности, мы вставили барьеры памяти в ключевые моменты при написании в управляемую кучу, чтобы гарантировать безопасность типа, но мы сделали это только с минимальным воздействием сверху производительность. Команда провела несколько обзоров проектов с экспертами, чтобы убедиться, что методы, применяемые в CLR ARM, были правильными. Более того, тесты производительности показывают, что производительность выполнения кода .NET оценивается так же, как и собственный код на C++ при сравнении между x86, x64 и ARM.