Новая операция называется «сравнить и обменять» и реализована в виде функций-членов compare_exchange_weak() и compare_exchange_strong(). Эта операция — краеугольный камень программирования с использованием атомарных типов; она сравнивает значение атомарной переменной с указанным ожидаемым значением и, если они совпадают, то сохраняет указанное новое значение. Если же значения не совпадают, то ожидаемое значение заменяется фактическим значением атомарной переменной. Функции сравнения и обмена возвращают значение типа bool, равное true, если сохранение было произведено, и false — в противном случае.
В случае compare_exchange_weak() сохранение может не произойти, даже если текущее значение совпадает с ожидаемым. В таком случае значение переменной не изменится, а функция вернет false. Такое возможно на машинах, не имеющих аппаратной команды сравнить-и-обменять, если процессор не может гарантировать атомарности операции — например, потому что поток, в котором операция выполнялась, был переключён в середине требуемой последовательности команд и замещен другим потоком (когда потоков больше, чем процессоров). Эта ситуация называется
Поскольку compare_exchange_weak() может стать жертвой ложного отказа, обычно ее вызывают в цикле:
bool expected = false;
extern atomic
while (!b.compare_exchange_weak(expected, true) && !expected);
Этот цикл продолжается, пока expected равно false, что указывает на ложный отказ compare_exchange_weak().
С другой стороны, compare_exchange_strong() гарантированно возвращает false только в том случае, когда текущее значение не было равно ожидаемому (expected). Это устраняет необходимость в показанном выше цикле, когда нужно только узнать, удалось ли нам изменить переменную или другой поток добрался до нее раньше.
Если мы хотим изменить переменную, каким бы ни было ее текущее значение (при этом новое значение может зависеть от текущего), то обновление expected оказывается полезной штукой; на каждой итерации цикла expected перезагружается, так что если другой поток не модифицирует значение в промежутке, то вызов compare_exchange_weak() или compare_exchange_strong() должен оказаться успешным на следующей итерации. Если новое сохраняемое значение вычисляется просто, то выгоднее использовать compare_exchange_weak(), чтобы избежать двойного цикла на платформах, где compare_exchange_weak() compare_exchange_strong() содержит цикл). С другой стороны, если вычисление нового значения занимает длительное время, то имеет смысл использовать compare_exchange_strong(), чтобы не вычислять значение заново, когда expected не изменилась. Для типа std::atomic это не столь существенно — в конце концов, есть всего два возможных значения — но для более широких атомарных типов различие может оказаться заметным.
Функции сравнения и обмена необычны еще и тем, что могут принимать memory_order_acq_rel, а при неудачном — memory_order_relaxed. В случае отказа функция сохранить-и-обменять не производит сохранение, поэтому семантика memory_order_release или memory_order_acq_rel неприменима. Поэтому задавать эти варианты упорядочения для отказа не разрешается. Кроме того, нельзя задавать для отказа более строгое упорядочение, чем для успеха; если вы требуете семантику memory_order_acquire или memory_order_seq_cst в случае отказа, то должны потребовать такую же и в случае успеха.
Если упорядочение для отказа не задано, то предполагается, что оно такое же, как для успеха, с тем отличием, что часть release заменяется: memory_order_release становится memory_order_relaxed, a memory_order_acq_rel — memory_order_acquire. Если не задано ни одно упорядочение, то как обычно предполагается memory_order_seq_cst, то есть полное последовательное упорядочение доступа как в случае успеха, так и в случае отказа. Следующие два вызова compare_exchange_weak() эквивалентны:
std::atomic
bool expected;