b.compare_exchange_weak(expected, true,

memory_order_acq_rel, memory_order_acquire);

b.compare_exchange_weak(expected, true, memory_order_acq_rel);

К чему приводит задание того или иного упорядочения, я расскажу в разделе 5.3.

Еще одно отличие std::atomic от std::atomic_flag заключается в том, что тип std::atomic не обязательно свободен от блокировок; для обеспечения атомарности реализация библиотеки может захватывать внутренний мьютекс. В тех редких случаях, когда это важно, можно с помощью функции-члена is_lock_free() узнать, являются ли операции над std::atomic свободными от блокировок. Это еще одна особенность, присущая всем атомарным типам, кроме std::atomic_flag.

Следующими по простоте являются атомарные специализации указателей std::atomic.

<p>5.2.4. Операции над <code>std::atomic<t*></t*></code>: арифметика указателей</p>

Атомарная форма указателя на тип Tstd::atomic — выглядит так же, как атомарная форма bool (std::atomic). Интерфейс по существу такой же, только операции применяются к указателям на значения соответствующего типа, а не к значениям типа bool. Как и в случае std::atomic, копирующие конструктор и оператор присваивания не определены, но разрешено конструирование и присваивание на основе подходящих указателей. Помимо обязательной функции is_lock_free(), тип std::atomic располагает также функциями load(), store(), exchange(), compare_exchange_weak() и compare_exchange_strong() с такой же семантикой, как std::atomic, но принимаются и возвращаются значения типа T*, а не bool.

Новыми в типе std::atomic являются арифметические операции над указателями. Базовые операции предоставляются функциями-членами fetch_add() и fetch_sub(), которые прибавляют и вычитают целое число из сохраненного адреса, а также операторы +=, -=, ++ и -- (последние в обеих формах — пред и пост), представляющие собой удобные обертки вокруг этих функций. Операторы работают так же, как для встроенных типов: если x — указатель std::atomic на первый элемент массива объектов типа Foo, то после выполнения оператора x+=3 x будет указывать на четвертый элемент и при этом возвращается простой указатель Foo*, который также указывает на четвертый элемент. Функции fetch_add() и fetch_sub() отличаются от операторов тем, что возвращают старое значение (то есть x.fetch_add(3) изменит x, так что оно будет указывать на четвертый элемент, но вернет указатель на первый элемент массива). Эту операцию еще называют обменять-и-прибавить, она относится к категории атомарных операций чтения-модификации-записи, наряду с exchange(), compare_exchange_weak() и compare_exchange_strong(). Как и другие операции такого рода, fetch_add() возвращает простой указатель T*, а не ссылку на объект std::atomic, поэтому вызывающая программа может выполнять действия над прежним значением:

class Foo{};

Foo some_array[5];              │Прибавить 2 к p

std::atomic p(some_array);│и вернуть старое

Foo* x = p.fetch_add(2);       ←┘значение

assert(x == some_array);

assert(p.load() == &some_array[2]);

x = (p -= 1);                     ←┐ Вычесть 1 из p

assert(x == &some_array[1]);       │и вернуть новое

assert(p.load() == &some_array[1]);│значение

Функциям можно также передать в дополнительном аргументе семантику упорядочения доступа к памяти:

p.fetch_add(3, std::memory_order_release);

Поскольку fetch_add() и fetch_sub() — операции чтения-модификации-записи, то они принимают любую семантику упорядочения и могут участвовать в последовательности освобождений. Для операторных форм задать семантику невозможно, поэтому предполагается семантика memory_order_sеq_cst.

Все прочие атомарные типы по существу одинаковы: это атомарные целочисленные типы с общим интерфейсом, различаются они только ассоциированными встроенными типами. Поэтому я опишу их все сразу.

<p>5.2.5. Операции над стандартными атомарными целочисленными типами</p>
Перейти на страницу:

Похожие книги