Введение

Инструкция ЦПУ для изменения значения в памяти только в том случае, если оно равно заданному значению. В информатике, операция "сравнение и обмен" (CAS) — это атомарная инструкция, используемая в многопоточном программировании для обеспечения синхронизации. Она сравнивает содержимое ячейки памяти с заданным значением и, только если они совпадают, изменяет содержимое этой ячейки памяти на новое заданное значение. Это выполняется как единая атомарная операция. Атомарность гарантирует, что новое значение вычисляется на основе актуальной информации; если значение было изменено другой нитью в это время, запись будет отклонена. Результат операции должен указывать, была ли выполнена замена; это можно сделать либо с помощью простого логического ответа (этот вариант часто называют "сравнение и установка"), либо путем возврата значения, прочитанного из ячейки памяти (а не значения, записанного в нее).

Проблема АБА

Некоторые алгоритмы, основанные на CAS, подвержены проблеме ложного срабатывания или проблеме ABA и должны ее учитывать. Возможно, что между моментом чтения старого значения и попыткой CAS другие процессоры или потоки изменят ячейку памяти два или более раз, в результате чего она примет битовый шаблон, совпадающий со старым значением. Проблема возникает, если этот новый битовый шаблон, выглядящий точно как старое значение, имеет другое значение: например, это может быть повторно использованный адрес или счетчик версий, вернувшийся к исходному значению. Общим решением является использование CAS двойной длины (DCAS). Например, на 32-битной системе можно использовать 64-битное CAS. Вторая половина используется для хранения счетчика. Операция сравнения сравнивает ранее прочитанное значение указателя и счетчика с текущими значениями указателя и счетчика. Если они совпадают, происходит обмен – записывается новое значение, но новое значение содержит инкрементированный счетчик. Это означает, что даже если произошла ситуация ABA, значение счетчика, скорее всего, не будет совпадать (для 32-битного значения потребуется кратное 2<sup>32</sup> количеству операций, чтобы счетчик вернулся к исходному значению, и в этот момент значение указателя также случайно должно быть таким же). Альтернативным вариантом (полезным на процессорах, не поддерживающих DCAS) является использование индекса в списке свободных блоков вместо полного указателя, например, при использовании 32-битного CAS можно использовать 16-битный индекс и 16-битный счетчик. Однако уменьшение разрядности счетчика делает ситуацию ABA более вероятной при современных скоростях процессоров. Один из простых способов смягчить эту проблему – хранить счетчик ABA в каждом элементе структуры данных, а не использовать единый счетчик ABA для всей структуры. Более сложное, но эффективное решение – реализовать безопасную регенерацию памяти (SMR). По сути, это сборка мусора без блокировок. Преимущество использования SMR заключается в гарантии того, что данный указатель будет существовать только один раз в структуре данных, что полностью решает проблему ABA. (Без SMR будет использоваться, например, список свободных блоков, чтобы обеспечить безопасный доступ ко всем элементам данных (без нарушений доступа к памяти), даже если они больше не присутствуют в структуре данных. С SMR доступ осуществляется только к элементам, которые в данный момент находятся в структуре данных).

Затраты и выгоды

CAS и другие атомные инструкции иногда считают излишними в однопроцессорных системах, поскольку атомарность любой последовательности инструкций можно обеспечить, отключая прерывания во время её выполнения. Однако отключение прерываний имеет множество недостатков. Например, коду, которому разрешено это делать, необходимо доверять, чтобы он не был злонамеренным и не монополизировал процессор, а также чтобы он был корректным и случайно не привел к зависанию системы в бесконечном цикле или ошибке страницы. Более того, отключение прерываний часто признается слишком затратным для практического применения. Таким образом, даже программы, предназначенные для работы только на однопроцессорных машинах, выиграют от использования атомных инструкций, как это происходит с futexes в Linux. В многопроцессорных системах обычно невозможно отключить прерывания на всех процессорах одновременно. Даже если бы это было возможно, два или более процессоров могли бы одновременно пытаться получить доступ к памяти одного и того же семафора, и, следовательно, атомарность не была бы достигнута. Инструкция "сравнить и заменить" (compare and swap) позволяет любому процессору атомарно проверять и изменять ячейку памяти, предотвращая подобные коллизии между процессорами. На многопроцессорных архитектурах серверного класса 2010-х годов операция "сравнить и заменить" относительно недорога по сравнению с простой загрузкой, не обслуживаемой из кэша. В исследовании 2013 года отмечается, что CAS всего в 1,15 раза дороже, чем загрузка без кэша на Intel Xeon (Westmere EX), и в 1,35 раза дороже на AMD Opteron (Magny Cours).

Реализация

Сравнение и обмен (и сравнение и обмен с двойной точностью) является неотъемлемой частью архитектуры IBM 370 (и всех последующих) с 1970 года. Операционные системы, работающие на этих архитектурах, широко используют эту инструкцию для обеспечения параллелизма процессов (то есть системных и пользовательских задач) и процессоров (то есть центральных процессоров), максимально устраняя "блокировки ожидания с отключением прерываний", которые использовались в более ранних операционных системах IBM. Аналогично, использование инструкции "test and set" также было исключено. В этих операционных системах новые единицы работы могут быть созданы "глобально" – в глобальный список приоритетов обслуживания, или "локально" – в локальный список приоритетов обслуживания, посредством выполнения одной инструкции сравнения и обмена. Это значительно повысило отзывчивость этих операционных систем. В архитектурах x86 (начиная с 80486) и Itanium это реализовано как инструкция compare and exchange (CMPXCHG) (на многопроцессорных системах необходимо использовать префикс). По состоянию на 2013 год большинство многопроцессорных архитектур поддерживают CAS аппаратно, а операция сравнения и обмена является наиболее популярным примитивом синхронизации для реализации как блокировочных, так и неблокирующих конкурентных структур данных.

Расширения

Поскольку CAS работает с одним местом памяти размером с указатель, а большинству алгоритмов без блокировок и ожидания необходимо изменять несколько мест, было реализовано несколько расширений. Двойное сравнение и обмен (DCAS) сравнивает два несвязанных места памяти с двумя ожидаемыми значениями и, если они равны, устанавливает оба места в новые значения. Обобщение DCAS на несколько (несоседних) слов называется MCAS или CASN. DCAS и MCAS представляют практический интерес для удобной (параллельной) реализации некоторых структур данных, таких как деки или двоичные деревья поиска. DCAS и MCAS могут быть реализованы, однако, с использованием более выразительной аппаратной транзакционной памяти, присутствующей в некоторых современных процессорах, таких как IBM POWER8 или в процессорах Intel, поддерживающих расширения транзакционной синхронизации (TSX). Сравнение и обмен двойной ширины работает с двумя смежными местами памяти размером с указатель (или, эквивалентно, с одним местом в два раза большим, чем указатель). На более поздних процессорах x86 инструкции CMPXCHG8B и CMPXCHG16B выполняют эту роль, хотя ранние 64-битные процессоры AMD не поддерживали CMPXCHG16B (современные процессоры AMD поддерживают). Некоторые материнские платы Intel эпохи Core 2 также затрудняют его использование, хотя процессоры его поддерживают. Эти проблемы привлекли внимание при запуске Windows 8.1, поскольку для ее работы требовалась аппаратная поддержка CMPXCHG16B. Сравнение с одним указателем и двойной обмен сравнивает один указатель, но записывает два. Инструкция Itanium cmp8xchg16 реализует это, при этом два записанных указателя находятся рядом. Многословное сравнение и обмен является обобщением обычного сравнения и обмена. Оно может использоваться для атомарного обмена произвольным количеством произвольно расположенных мест памяти. Обычно многословное сравнение и обмен реализуется программно с использованием обычных операций сравнения и обмена двойной ширины. Недостатком этого подхода является отсутствие масштабируемости. Постоянное сравнение и обмен — это комбинация операции сохранения (persist) и обычной операции сравнения и обмена. Оно может использоваться для атомарного сравнения и обмена значениями, а затем сохранения значения, чтобы не было разрыва между параллельной видимостью и видимостью при сбое. Это расширение решает проблему чтения непостоянных записей.

Основные алгоритмы, реализованные с использованием CAS

2003 дискуссия "Lock Free using cmpxchg8b" на Intel x86, со ссылками на различные статьи и исходный код.