Тип std::atomic_flag
настолько ограничен, что его даже нельзя использовать в качестве обычного булевского флага, так как он не допускает проверки без изменения значения. На эту роль больше подходит тип std::atomic
, который я рассмотрю ниже.
5.2.3. Операции над std::atomic
Из атомарных целочисленных типов простейшим является std::atomic
. Как и следовало ожидать, его функциональность в качестве булевского флага богаче, чем у std::atomic_flag
. Хотя копирующий конструктор и оператор присваивания по-прежнему не определены, но можно сконструировать объект из неатомарного bool
, поэтому в начальном состоянии он может быть равен как true
, так и false
. Разрешено также присваивать объектам типа std::atomic
значения неатомарного типа bool
:
std::atomic b(true);
b = false;
Что касается оператора присваивания с неатомарным bool
в правой части, нужно еще отметить отход от общепринятого соглашения о возврате ссылки на объект в левой части — этот оператор возвращает присвоенное значение типа bool
. Такая практика обычна для атомарных типов: все поддерживаемые ими операторы присваивания возвращают значения (соответствующего неатомарного типа), а не ссылки. Если бы возвращалась ссылка на атомарную переменную, то программа, которой нужен результат присваивания, должна была бы явно загрузить значение, открывая возможность для модификации результата другим потоком в промежутке между присваиванием и чтением. Получая же результат присваивания в виде неатомарного значения, мы обходимся без дополнительной операции загрузки и можем быть уверены, что получено именно то значение, которое было сохранено.
Запись (любого значения: true
или false
) производится не чрезмерно ограничительной функцией clear()
из класса std::atomic_flag
, а путём вызова функции-члена store()
, хотя семантику упорядочения доступа к памяти по-прежнему можно задать. Аналогично вместо test_and_set()
используется более общая функция-член exchange()
, которая позволяет атомарно заменить ранее сохраненное значение новым и вернуть прежнее значение. Тип std::atomic
поддерживает также проверку значения без модификации посредством неявного преобразования к типу bool
или явного обращения к функции load()
. Как нетрудно догадаться, store()
— это операция сохранения, load()
— операция загрузки, a exchange()
— операция чтения-модификации-записи:
std::atomic b;
bool x = b.load(std::memory_order_acquire);
b.store(true);
x = b.exchange(false, std::memory_order_acq_rel);
Функция exchange()
— не единственная операция чтения-модификации-записи, которую поддерживает тип std::atomic
; в нем также определена операция сохранения нового значения, если текущее совпадает с ожидаемым.
Сохранение (или несохранение) нового значения в зависимости от текущего
Новая операция называется «сравнить и обменять» и реализована в виде функций-членов compare_exchange_weak()
и compare_exchange_strong()
. Эта операция — краеугольный камень программирования с использованием атомарных типов; она сравнивает значение атомарной переменной с указанным ожидаемым значением и, если они совпадают, то сохраняет указанное новое значение. Если же значения не совпадают, то ожидаемое значение заменяется фактическим значением атомарной переменной. Функции сравнения и обмена возвращают значение типа bool
, равное true
, если сохранение было произведено, и false
— в противном случае.
В случае compare_exchange_weak()
сохранение может не произойти, даже если текущее значение совпадает с ожидаемым. В таком случае значение переменной не изменится, а функция вернет false
. Такое возможно на машинах, не имеющих аппаратной команды сравнить-и-обменять, если процессор не может гарантировать атомарности операции — например, потому что поток, в котором операция выполнялась, был переключён в середине требуемой последовательности команд и замещен другим потоком (когда потоков больше, чем процессоров). Эта ситуация называется ложным отказом , потому что причиной отказа являются не значения переменных, а хронометраж выполнения функции.
Поскольку compare_exchange_weak()
может стать жертвой ложного отказа, обычно ее вызывают в цикле:
bool expected = false;
extern atomic b; // установлена где-то в другом месте
while (!b.compare_exchange_weak(expected, true) && !expected);
Этот цикл продолжается, пока expected
равно false
, что указывает на ложный отказ compare_exchange_weak()
.
Читать дальше