Методы Interlocked на SMP-системах

На симметричных многопроцессорных системах Intel (SMP) простое чтение и запись элементов памяти встроенного размера синхронизируются автоматически.

В системе IA-32 чтение и запись свойств, выровненных по 32-битным значениям, синхронизированы. Поэтому в предыдущем примере, где было показано применение Interlocked.CompareExchange для простого чтения значения int32, вызов этого метода был бы не обязательным, если переменная правильно выровнена в памяти.

По умолчанию CLR выполняет немало действий, чтобы правильно выровнять значения по естественным границам. Однако можно переопределить размещение значений в классе или структуре, применив к полям атрибут FieldOffsetAttribute, тем самым отменяя выравнивание полей данных.

Если значение Int32 не выровнено, то гарантии, упомянутые в предыдущем абзаце, будут утеряны. В таком случае для надежного чтения значения должен применяться метод Interlocked.CompareExchange. Все методы Interlocked. . . реализованы в системах IA-32 с использованием префикса lock.

Данный префикс заставляет процессор применять сигнал lock#, а это предотвращает параллельный доступ к значению других процессоров системы, что и требуется в сложных операциях, когда увеличиваются значения счетчиков и тому подобное.

Одно удобное качество префикса lock заключается в том, что не выровненные поля данных не повлияют пагубным образом на целостность блокировки. Другими словами, он отлично работает с не выровненными данными. Вот почему Interlocked.CompareExchange — гарантия атомарного чтения не выровненных данных.

И, наконец, примите во внимание тот факт, что в классе Interlocked реализованы перегрузки некоторых методов, позволяющие им работать с 64-битными значениями, числами с плавающей точкой и ссылками на объекты.

В действительности в Interlocked. . . даже предлагаются обобщенные перегрузки для работы со ссылками на объекты любых типов. Рассмотрим, что означает атомарно работать с 64-битными значениями на 32-разрядных системах.

Естественно, не существует способа атомарно читать такие значения без обращения к классу Interlocked. Фактически по этой причине в версии .NET 2.0 класса Interlocked предусмотрен метод Interlocked.Read для значений типа Int64. Конечно, такой метод не нужен на 64-разрядных системах, где он сводится к обычному чтению.

Однако среда CLR предназначена для работы на многих платформах, поэтому при манипуляциях с 64-битными значениями всегда следует применять Interlocked.Read.

По этим причинам лучше перестраховаться, чем потом сожалеть, и воспользоваться Interlocked.CompareExchange для чтения и записи значений в атомарном режиме, поскольку очень трудно проверить факт наличия или отсутствия выравнивания или превышения системного размера атомарных данных, прежде чем читать или писать их в “сыром” виде.

Определение системного размера атомарной единицы данных для платформы и построение кода на основе этой информации совершенно противоречит межплатформенному духу управляемого кода.

И последний момент, на который следует обратить внимание — ключевое слово volatile в рассмотренном примере. Обращаясь к переменным в свободной от блокировок манере и полагаясь на автоматическую синхронизацию процессора в многопоточной среде, необходимо снабжать такие переменные ключевым словом volatile.

В противном случае компилятор может внести различные отклонения в процессе оптимизации кода, которые могут изменить логику кода. Ключевое слово volatile помимо прочего препятствует такого рода оптимизации, и гарантирует, что код в полной мере будет выполняться именно в том порядке, как он написан.

Когда методы класса Interlocked используются со значениями, помеченными volatile, компилятор выдаст предупреждение CS0420, начинающееся со слов “a reference to a volatile field will not be treated as volatile” (ссылка на поле volatile не будет трактоваться как volatile).

Если вы прочтете описание этого предупреждения в документации MSDN, то увидите, что одним из исключений из правила является вызов методов Interlocked. Поэтому в данном случае можно успешно подавить выдачу этих предупреждений с помощью директивы #pragma warning.

В дополнение предположим, что имеется два 32-битных целых, представляющих две блокировки по имени lock1 и lоск2. Пусть также система требует, чтобы lock1 устанавливалась перед lockl2.

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

Для решения этой проблемы потребуется поместить вызов Thread.MemoryBarrier между обращениями к этим двум переменным. Метод Thread.MemoryBarrier обеспечит сохранение заданного порядка.

Вы можете следить за любыми ответами на эту запись через RSS 2.0 ленту. Вы можете оставить ответ, или trackback с вашего собственного сайта.

Оставьте отзыв

XHTML: Вы можете использовать следующие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

 
Rambler's Top100