Оцените выбор порядка памяти для неудачной попытки compare_exchange: может ли он быть release?
Нет. Неудачная операция compare_exchange фактически выполняет только атомарное чтение: запись нового значения не произошла, поэтому порядок release или acq_rel для неудачного исхода недопустим. Для отказа обычно выбирают relaxed, если нужно лишь повторить попытку, или acquire, если прочитанное состояние должно сделать видимыми данные, опубликованные другим потоком.
Атомарное сравнение с обменом появилось как базовый строительный блок неблокирующих алгоритмов: стеки, очереди, счётчики состояний и таблицы указателей должны были изменять значение без мьютекса. Операция имеет два разных исхода — успешную запись и неудачное чтение фактического значения, поэтому одинаковый порядок памяти для них не всегда оправдан.
Разделение порядков позволяет не платить за синхронизацию там, где неудачная попытка используется только для обнаружения конфликта. Это особенно важно в циклах повторных попыток, выполняемых при высокой конкуренции.
При успехе compare_exchange сравнивает атомарное значение с ожидаемым и, если они совпали, записывает новое значение. При отказе запись не выполняется, а переменная, переданная как expected, заменяется фактически прочитанным значением.
Ошибочный порядок памяти может привести к некорректной программе или к нарушению требований интерфейса атомарной операции. Слишком слабый порядок также опасен: поток может увидеть новое состояние атомика, но не получить гарантии видимости обычных данных, связанных с этим состоянием.
Неудачный compare_exchange является операцией чтения. Поэтому порядок release не имеет смысла: release распространяет предшествующие записи через операцию, которая должна выступать источником публикации, а отказ не записывает новое значение атомика. По той же причине недопустим составной порядок acq_rel.
Для неудачи допустимы relaxed, acquire и seq_cst, если выбранный порядок не сильнее порядка успешной операции. На практике relaxed применяют, когда после отказа выполняется только повторная попытка, а acquire — когда поток должен безопасно читать данные, опубликованные через наблюдаемое значение атомика.
Минимальный пример выбора порядков:
При успехе операция получает acquire-гарантию для чтения ранее опубликованных данных и release-гарантию для публикации собственных записей. При отказе выполняется только acquire-чтение текущего state; значение expected заменяется фактическим. Если результат отказа используется лишь для продолжения цикла и никакие связанные данные не читаются, memory_order_relaxed обычно является достаточным и более дешёвым вариантом.
Порядок отказа не должен быть сильнее порядка успеха. Например, нельзя использовать seq_cst для отказа при relaxed для успеха; также нельзя указывать для отказа release или acq_rel. Это ограничение отражает реальную семантику операции, а не только требование оптимизации.
В lock-free таблице поток пытается перевести слот из состояния empty в occupied. При конфликте другой поток уже изменил слот, и текущий поток получает новое состояние в expected.
Вариант с relaxed для успеха и отказа минимален по стоимости, но подходит только тогда, когда слот содержит исключительно управляющее состояние либо последующая синхронизация выполняется отдельно. Вариант с acq_rel при успехе и relaxed при отказе эффективен для цикла, который при конфликте просто перечитывает состояние.
Если после неудачи поток сразу обращается к данным, опубликованным владельцем нового состояния, отказ должен иметь acquire-семантику. Такой вариант был бы выбран для таблицы, где переход состояния одновременно публикует содержимое записи: он сохраняет корректную видимость данных и не усиливает порядок успешной операции без необходимости.
Потому что атомарная операция должна сообщить вызывающему коду, какое значение фактически наблюдалось. Это позволяет построить следующий шаг цикла без отдельной атомарной загрузки. В случае compare_exchange_weak значение также может остаться тем же при ложном отказе, поэтому алгоритм обычно повторяет попытку с обновлённым или прежним expected.
Да, если атомарное чтение наблюдает значение, записанное release-операцией или соответствующей release-последовательностью. Тогда acquire-порядок неудачной попытки может установить отношение happens-before и сделать видимыми обычные записи, предшествовавшие публикации. Само наличие acquire такой гарантии не создаёт: необходима связь с конкретной release-операцией.
Потому что при отказе запись нового значения не происходит. Если алгоритму действительно требуется release-действие, его нужно выполнить отдельной операцией или добиться успешного compare-exchange; если требуется только чтение опубликированного состояния, достаточно acquire. Указание acq_rel не добавляет отсутствующую запись, а нарушает ограничения порядка памяти для неудачного исхода.