Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ответы ОСиСП.doc
Скачиваний:
68
Добавлен:
11.05.2015
Размер:
1.78 Mб
Скачать

Поддержка volatile-полей в с#

Сложно сделать так, чтобы все программисты вызывали методы VolatileRead и VolatileWrite корректно. Разработчикам сложно помнить обо всем этом и прогнозировать, что могут делать с общими данными другие потоки, работающие в фоновом режиме. Компилятор C# предлагает ключевое слово volatile, которое можно применять к статическим полям или экземплярам полей следующих типов: Byte, SByte,Intl6, UIntl6,Jnt32, UInt32, Char, Single или Boolean. Это ключевое слово можно также использовать для ссылки на типы и любые поля перечислений, если в основе перечислимого типа лежит тип Byte, SByte, Intl6, UIntl6, Int32, UInt32, Single или Boolean. Компилятор JIT гарантирует, что все обращения к volatile-полям выполняются по механизму временного считывания и записи, так что не обязательно явно вызывать какие-либо статические методы VolatileXxx. Кроме того, ключевое слово volatile сообщает компиляторам С# и JIT, что поле не нужно кешировать в регистре процессора, в этом случае во всех операциях с этим полем будет использоваться значение, считанное из основной памяти.

Ключевое слово volatile позволяет на основе кода класса VolatileMethod создать новый класс VolatileField:

internal sealed class VolatileField {

private volatile Byte uninitialized = 0; private Int32 m_value = 0;

// Этот метод выполняется одним потоком, public void Thread1() {

m_value =5;

uninitialized = 1;

// Этот метод выполняется другим потоком, public void Thread2() {

if (uninitialized == 1) {

// Если выполняется эта строка, отобразится значение 5.

Console.WriteLine(m_value); }

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

m_amount = m_amount + m_amount; // m_amount - определенное в классе поле. m_amount *= m_amount

Кроме того, С# не поддерживает передачу volatile-полей в метод по ссылке. Например, если поле m_amount определено как volatile Int32, при попытке вызвать метод TryParse типа Int32 компилятор выдаст предупреждение:

Boolean success = Int32.TryParse("123", out m_amount);

// При обработке этой строки кода компилятор С# покажет следующее предупреждение:

// CS0420: a reference to a volatile field will not be treated as volatile

// (CS0420: ссылка на volatile-поле не будет считаться временной).

Создавая JIT-компилятор для архитектуры IA64, разработчики CLR поняли, что у многих программистов (включая их самих) есть код, который не будет корректно работать при вызове не из временных (неупорядоченных) операций записи и чтения. Поэтому они решили, что JIT-компилятор IA64 должен всегда создавать инструкции чтения, включающие семантику запроса, а операции записи должны всегда включать семантику освобождения. Это позволило существующим приложениям, работающим в архитектуре х8б, работать без ошибок и в архитектуре IA64. К сожалению, это сильно ударило бы по производительности, поэтому пришлось пойти на компромисс. Во всех операциях записи в JIT-компилятореIА64 используется семантика освобождения, а чтение выполняется в обычном порядке. Чтобы выполнить чтение с семантикой запроса, нужно вызвать метод VolatileRead или применить ключевое слово volatile. Это намного более понятная модель управления памятью, которую разработчики вполне в состоянии освоить.

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

Примечание:Как говорилось в главе 1, ассоциация ЕСМА определила стандартную версию CLR, названную Common Language Infrastructure (CLI). В документации ЕСМА описывается модель памяти, которой должны соответствовать все CLI-совместимые среды времени выполнения. В этой версии модели памяти все операции чтения и записи являются неупорядоченными, если только программист явно не вызывает метод VolatileRead или VolatileWrite или не использует ключевое слово volatile. Это также объясняет, почему FCL все еще содержит методы VolatileWrite, хотя они бесполезны, если приложение работает в CLR, созданной компанией Microsoft. В Microsoft посчитали модель ЕСМА слишком сложной и непонятной, поэтому реализовали собственную версию CLI (то есть CLR), использующую более сильную модель памяти, в которой все операции записи выполняются с семантикой освобождения. Такое изменение позволяет считать CLR компании Microsoft совместимой со стандартом, так как любые приложения, написанные для модели памяти ЕСМА, могут работать в CLR от Microsoft.

Однако существует возможная проблема, заключающаяся в том, что кто-то может написать приложение и протестировать его при помощи CLR компании Microsoft. Это приложение может отлично работать, но в другой реализации CLI оно может не работать. Было бы лучше для всех, если бы стандарт ЕСМА и все реализации этого стандарта придерживались единой модели памяти. Microsoft надеется, что в следующих версиях стандарта ЕСМА будет принята модель памяти, которая сейчас применяется в CLR.

Наконец, нужно сказать, что всегда, когда поток вызывает Interlocked-метод (см. следующий раздел), процессор принудительно обеспечивает согласованность кеша. Так что при работе с переменными при помощи Interlocked-методов не нужно беспокоиться о соответствии этой модели памяти. Кроме того, все блокировки синхронизации потоков (Monitor, ReaderWriterLock,Mutex, Semaphore, AutoResetEvent, ManualResetEvent и другие) вызывают Interlocked-мегоды, так что программисту также не приходится заботиться о моделях памяти.

Внимание! В общем случае, я настоятельно не рекомендую вызывать методы VolatileRead и VolatileWrite, а также ключевое слово volatile. Вместо этого лучше использовать Interlocked-методы или конструкции синхронизации потоков более высокого уровня. Эти методы работают всегда, независимо от модели памяти и платформы процессора.