У любой операции над атомарными типами имеется необязательный аргумент, задающий требования к семантике упорядочения доступа к памяти. Точный смысл различных вариантов упорядочения обсуждается в разделе 5.3. Пока же достаточно знать, что операции разбиты на три категории.

• Операции сохранения, для которых можно задавать упорядочение memory_order_relaxed, memory_order_release и memory_оrder_sеq_cst.

• Операции загрузки, для которых можно задавать упорядочение memory_order_relaxed, memory_order_consume, memory_order_acquire и memory_order_seq_cst.

• Операции чтения-модификации-записи, для которых можно задавать упорядочение memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_release, memory_order_acq_rel и memory_order_seq_cst.

По умолчанию для всех операций подразумевается упорядочение memory_оrder_sеq_cst.

Теперь рассмотрим, какие операции можно производить над каждым из стандартных атомарных типов, начиная с std::atomic_flag.

<p>5.2.2. Операции над <code>std::atomic_flag</code></p>

Простейший стандартный атомарный тип std::atomic_flag представляет булевский флаг. Объекты этого типа могут находиться в одном из двух состояний: установлен или сброшен. Этот тип намеренно сделан максимально простым, рассчитанным только на применение в качестве строительного блока. Поэтому увидеть его в реальной программе можно лишь в очень специфических обстоятельствах. Тем не менее, он послужит нам отправной точкой для обсуждения других атомарных типов, потому что на его примере отчетливо видны общие относящиеся к ним стратегии.

Объект типа std::atomic_flag должен быть инициализирован значением ATOMIC_FLAG_INIT. При этом флаг оказывается в состоянии сброшен. Никакого выбора тут не предоставляется — флаг всегда должен начинать существование в сброшенном состоянии:

std::atomic_flag f = ATOMIC_FLAG_INIT;

Требование применяется вне зависимости от того, где и в какой области видимости объект объявляется. Это единственный атомарный тип, к инициализации которого предъявляется столь специфическое требование, зато при этом он является также единственным типом, гарантированно свободным от блокировок. Если у объекта std::atomic_flag статический класс памяти, то он гарантированно инициализируется статически, и, значит, никаких проблем с порядком инициализации не будет — объект всегда оказывается инициализированным к моменту первой операции над флагом.

После инициализации с флагом можно проделать только три вещи: уничтожить, очистить или установить, одновременно получив предыдущее значение. Им соответствуют деструктор, функция-член clear() и функция-член test_and_set(). Для обеих функций clear() и test_and_set() можно задать упорядочение памяти. clear() — операция сохранения, поэтому варианты упорядочения memory_order_acquire и memory_order_acq_rel к ней неприменимы, a test_and_set() — операция чтения-модификации-записи, так что к ней применимы любые варианты упорядочения. Как и для любой атомарной операции, по умолчанию подразумевается упорядочение memory_order_seq_cst. Например:

f.clear(std::memory_order_release);←(1)

bool x = f.test_and_set();         ←(2)

Здесь при вызове clear() (1) явно запрашивается сброс флага с семантикой освобождения, а при вызове test_and_set() (2) подразумевается стандартное упорядочение для операции установки флага и получения прежнего значения.

Объект std::atomic_flag нельзя сконструировать копированием из другого объекта, не разрешается также присваивать один std::atomic_flag другому. Это не особенность типа std::atomic_flag, а свойство, общее для всех атомарных типов. Любые операции над атомарным типом должны быть атомарными, а для присваивания и конструирования копированием нужны два объекта. Никакая операция над двумя разными объектами не может быть атомарной. В случае копирования и присваивания необходимо сначала прочитать значение первого объекта, а потом записать его во второй. Это две отдельные операции над двумя различными объектами, и их комбинация не может быть атомарной. Поэтому такие операции запрещены.

Перейти на страницу:

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