7.9.1 Встроенные функции атомарных операций с учетом модели памяти
Следующие встроенные функции примерно соответствуют требованиям модели памяти C++11. Все они имеют префикс «__atomic», и большинство из них перегружены, поэтому работают с несколькими типами.
Эти функции предназначены для замены устаревших встроенных функций «__sync». Основное отличие заключается в том, что требуемый порядок памяти передается функциям в качестве параметра. В новом коде всегда следует использовать встроенные функции «__atomic», а не «__sync».
Обратите внимание, что встроенные функции «__atomic» предполагают соответствие программ модели памяти C++11. В частности, предполагается, что в программах отсутствуют гонки данных. Подробные требования см. в стандарте C++11.
Встроенные функции «__atomic» можно использовать с любыми целочисленными скалярными типами или типами указателей размером 1, 2, 4 или 8 байт. Также разрешены 16-байтовые целочисленные типы, если архитектура поддерживает «__int128» (см. 128-битные целые числа).
Для четырех неарифметических функций (загрузка, сохранение, обмен и compare_exchange) также существует универсальная версия. Она работает с типами данных любого вида. Если размер конкретного типа данных это позволяет, используется встроенная функция без блокировок; в противном случае остается внешний вызов, который разрешается во время выполнения. Формат этого внешнего вызова совпадает с обычным, за исключением добавленного первым параметром «size_t», указывающего размер объекта, на который указывает указатель. Размеры всех объектов должны совпадать.
Можно задать 6 различных порядков памяти. Они соответствуют одноименным порядкам памяти C++11; подробные определения см. в стандарте C++11 или на вики GCC, посвященной атомарной синхронизации. Для отдельных архитектур целевые платформы могут также поддерживать дополнительные порядки памяти. Подробности см. в документации целевой платформы.
Атомарная операция может как ограничивать перемещение кода, так и отображаться на аппаратные инструкции для синхронизации потоков (например, барьер). Степень этого воздействия определяется порядками памяти, перечисленными здесь примерно по возрастанию строгости. Описание каждого порядка памяти лишь приблизительно иллюстрирует его эффекты и не является спецификацией; точную семантику см. в модели памяти C++11.
__ATOMIC_RELAXEDНе накладывает ограничений на упорядочивание между потоками.
__ATOMIC_CONSUMEВ настоящее время реализован с использованием более строгого порядка памяти
__ATOMIC_ACQUIREиз-за недостатка в семантике C++11 дляmemory_order_consume.__ATOMIC_ACQUIREСоздает межпоточное отношение «происходит до» от операции сохранения со семантикой release (или более строгой) к этой операции загрузки acquire. Может препятствовать перемещению кода перед операцией.
__ATOMIC_RELEASEСоздает межпоточное отношение «происходит до» для операций загрузки со семантикой acquire (или более строгой), считывающих данные из этого сохранения release. Может препятствовать перемещению кода после операции.
__ATOMIC_ACQ_RELОбъединяет эффекты обоих порядков:
__ATOMIC_ACQUIREи__ATOMIC_RELEASE.__ATOMIC_SEQ_CSTОбеспечивает полный порядок для всех остальных операций
__ATOMIC_SEQ_CST.
Обратите внимание, что в модели памяти C++11 барьеры (например, «__atomic_thread_fence») действуют в сочетании с другими атомарными операциями над конкретными областями памяти (например, атомарными загрузками); операции над конкретными областями памяти не обязательно влияют на другие операции таким же образом.
Целевым архитектурам рекомендуется предоставлять собственные шаблоны для каждой из встроенных атомарных функций. Если целевой платформой шаблон не предоставлен, используются исходные встроенные атомарные функции «__sync», не учитывающие модель памяти, вместе с необходимыми барьерами синхронизации вокруг них для обеспечения требуемого поведения. В этом случае выполнение подчиняется тем же ограничениям, что и у этих встроенных функций.
Если отсутствует шаблон или механизм для создания последовательности инструкций без блокировок, выполняется вызов внешней процедуры с теми же параметрами, который разрешается во время выполнения.
При реализации шаблонов для этих встроенных функций параметр порядка памяти можно игнорировать, если шаблон реализует наиболее строгий порядок памяти __ATOMIC_SEQ_CST. С любым другим порядком памяти операции будут выполняться корректно и с этим порядком, но могут быть не такими эффективными, как при более подходящей реализации требований к relaxed-порядку.
Обратите внимание, что стандарт C++11 позволяет определять параметр порядка памяти во время выполнения, а не во время компиляции. Эти встроенные функции отображают любое значение, полученное во время выполнения, на __ATOMIC_SEQ_CST, а не вызывают библиотечную процедуру во время выполнения и не встраивают оператор switch. На данный момент это соответствует стандарту, безопасно и является наиболее простым подходом.
Параметр порядка памяти имеет тип signed int, но для порядка памяти зарезервированы только младшие 16 бит. Остальная часть signed int зарезервирована для целевой платформы и должна быть равна 0. Использование предопределенных атомарных значений обеспечивает корректное применение.
Архитектура x86 поддерживает дополнительные модификаторы порядка памяти для обозначения критических секций, в которых используется аппаратное устранение блокировок. Эти модификаторы можно побитово объединять со стандартным порядком памяти для атомарных встроенных функций.
__ATOMIC_HLE_ACQUIREНачать устранение блокировок для переменной-блокировки. Порядок памяти должен быть
__ATOMIC_ACQUIREили более строгим.__ATOMIC_HLE_RELEASEЗавершить устранение блокировок для переменной-блокировки. Порядок памяти должен быть
__ATOMIC_RELEASEили более строгим.
Если получение блокировки завершается неудачей, для обеспечения высокой производительности необходимо быстро прервать транзакцию. Это можно сделать с помощью _mm_pause.
#include <immintrin.h> // For _mm_pause
int lockvar;
/* Acquire lock with lock elision */
while (__atomic_exchange_n(&lockvar, 1, __ATOMIC_ACQUIRE|__ATOMIC_HLE_ACQUIRE))
_mm_pause(); /* Abort failed transaction */
...
/* Free lock with lock elision */
__atomic_store_n(&lockvar, 0, __ATOMIC_RELEASE|__ATOMIC_HLE_RELEASE);
-
Встроенная функция:
type__atomic_load_n(type *ptr, int memorder) -
Эта встроенная функция реализует атомарную операцию загрузки. Она возвращает содержимое
*ptr.Допустимые варианты порядка памяти:
__ATOMIC_RELAXED,__ATOMIC_SEQ_CST,__ATOMIC_ACQUIREи__ATOMIC_CONSUME.
-
Встроенная функция:
void__atomic_load(type *ptr, type *ret, int memorder) -
Это универсальная версия атомарной загрузки. Она возвращает содержимое
*ptrв*ret.
-
Встроенная функция:
void__atomic_store_n(type *ptr, type val, int memorder) -
Эта встроенная функция реализует атомарную операцию сохранения. Она записывает
valв*ptr.Допустимые варианты порядка памяти:
__ATOMIC_RELAXED,__ATOMIC_SEQ_CSTи__ATOMIC_RELEASE.
-
Встроенная функция:
void__atomic_store(type *ptr, type *val, int memorder) -
Это универсальная версия атомарного сохранения. Она сохраняет значение
*valв*ptr.
-
Встроенная функция:
type__atomic_exchange_n(type *ptr, type val, int memorder) -
Эта встроенная функция реализует атомарную операцию обмена. Она записывает val в
*ptrи возвращает предыдущее содержимое*ptr.Допустимы все варианты порядка памяти.
-
Встроенная функция:
void__atomic_exchange(type *ptr, type *val, type *ret, int memorder) -
Это универсальная версия атомарного обмена. Она сохраняет содержимое
*valв*ptr. Исходное значение*ptrкопируется в*ret.
-
Встроенная функция:
bool__atomic_compare_exchange_n(type *ptr, type *expected, type desired, bool weak, int success_memorder, int failure_memorder) -
Эта встроенная функция реализует атомарную операцию сравнения и обмена. Она сравнивает содержимое
*ptrс содержимым*expected. Если значения равны, операция является операцией чтения-изменения-записи, которая записывает desired в*ptr. Если значения не равны, операция является операцией чтения, а текущее содержимое*ptrзаписывается в*expected. Значение weak равноtrueдля слабого варианта compare_exchange, который может завершиться ложной неудачей, иfalseдля строгого варианта, который никогда не завершается ложной неудачей. Многие целевые платформы поддерживают только строгий вариант и игнорируют этот параметр. Если вы сомневаетесь, используйте строгий вариант.Если desired записывается в
*ptr, возвращаетсяtrue, а на память оказывается влияние в соответствии с порядком памяти, указанным параметром success_memorder. Ограничений на используемый здесь порядок памяти нет.В противном случае возвращается
false, а на память оказывается влияние в соответствии с параметром failure_memorder. Этот порядок памяти не может быть__ATOMIC_RELEASEили__ATOMIC_ACQ_REL. Он также не может быть строже порядка, заданного параметром success_memorder.
-
Встроенная функция:
bool__atomic_compare_exchange(type *ptr, type *expected, type *desired, bool weak, int success_memorder, int failure_memorder) -
Эта встроенная функция реализует универсальную версию
__atomic_compare_exchange. Функция практически идентична__atomic_compare_exchange_n, за исключением того, что желаемое значение также является указателем.
-
Встроенная функция:
type__atomic_add_fetch(type *ptr, type val, int memorder) -
Встроенная функция:
type__atomic_sub_fetch(type *ptr, type val, int memorder) -
Встроенная функция:
type__atomic_and_fetch(type *ptr, type val, int memorder) -
Встроенная функция:
type__atomic_xor_fetch(type *ptr, type val, int memorder) -
Встроенная функция:
type__atomic_or_fetch(type *ptr, type val, int memorder) -
Встроенная функция:
type__atomic_nand_fetch(type *ptr, type val, int memorder) -
Эти встроенные функции выполняют операцию, указанную в названии, и возвращают результат операции. Операции с указателями выполняются так, как если бы операнды имели тип
uintptr_t. То есть они не масштабируются по размеру типа, на который указывает указатель.{ *ptr op= val; return *ptr; } { *ptr = ~(*ptr & val); return *ptr; } // nandОбъект, на который указывает первый аргумент, должен иметь целочисленный тип или тип указателя. Он не должен иметь тип boolean. Допустимы все порядки памяти.
-
Встроенная функция:
type__atomic_fetch_add(type *ptr, type val, int memorder) -
Встроенная функция:
type__atomic_fetch_sub(type *ptr, type val, int memorder) -
Встроенная функция:
type__atomic_fetch_and(type *ptr, type val, int memorder) -
Встроенная функция:
type__atomic_fetch_xor(type *ptr, type val, int memorder) -
Встроенная функция:
type__atomic_fetch_or(type *ptr, type val, int memorder) -
Встроенная функция:
type__atomic_fetch_nand(type *ptr, type val, int memorder) -
Эти встроенные функции выполняют операцию, указанную в названии, и возвращают значение, которое до этого содержалось в
*ptr. Операции с указателями выполняются так, как если бы операнды имели типuintptr_t. То есть они не масштабируются по размеру типа, на который указывает указатель.{ tmp = *ptr; *ptr op= val; return tmp; } { tmp = *ptr; *ptr = ~(*ptr & val); return tmp; } // nandК аргументам применяются те же ограничения, что и к соответствующим встроенным функциям
__atomic_op_fetch. Допустимы все порядки памяти.
-
Встроенная функция:
bool__atomic_test_and_set(void *ptr, int memorder) -
Эта встроенная функция выполняет атомарную операцию test-and-set над байтом по адресу
*ptr. Байту присваивается некоторое ненулевое значение «установлено», определяемое реализацией; возвращаемое значение равноtrueтогда и только тогда, когда предыдущее содержимое было «установлено». Ее следует использовать только для операндов типаboolилиchar. Для других типов может быть установлен лишь фрагмент значения.Допустимы все порядки памяти.
-
Встроенная функция:
void__atomic_clear(bool *ptr, int memorder) -
Эта встроенная функция выполняет атомарную операцию очистки над
*ptr. После операции*ptrсодержит 0. Ее следует использовать только для операндов типаboolилиcharи совместно с__atomic_test_and_set. Для других типов может быть выполнена лишь частичная очистка. Если тип не являетсяbool, предпочтительно использовать__atomic_store.Допустимые варианты порядка памяти:
__ATOMIC_RELAXED,__ATOMIC_SEQ_CSTи__ATOMIC_RELEASE.
-
Встроенная функция:
void__atomic_thread_fence(int memorder) -
Эта встроенная функция действует как барьер синхронизации между потоками в соответствии с указанным порядком памяти.
Допустимы все порядки памяти.
-
Встроенная функция:
void__atomic_signal_fence(int memorder) -
Эта встроенная функция действует как барьер синхронизации между потоком и обработчиками сигналов, выполняющимися в том же потоке.
Допустимы все порядки памяти.
-
Встроенная функция:
bool__atomic_always_lock_free(size_t size, void *ptr) -
Эта встроенная функция возвращает
true, если объекты размером size байт всегда порождают атомарные инструкции без блокировок для целевой архитектуры. Значение size должно быть константой времени компиляции, и результат также вычисляется как константа времени компиляции.ptr — необязательный указатель на объект, который может использоваться для определения выравнивания. Значение 0 означает, что следует использовать обычное выравнивание. Компилятор также может игнорировать этот параметр.
if (__atomic_always_lock_free (sizeof (long long), 0))
-
Встроенная функция:
bool__atomic_is_lock_free(size_t size, void *ptr) -
Эта встроенная функция возвращает
true, если объекты размером size байт всегда порождают атомарные инструкции без блокировок для целевой архитектуры. Если неизвестно, что встроенная функция не использует блокировки, выполняется вызов процедуры времени выполнения с именем__atomic_is_lock_free.ptr — необязательный указатель на объект, который может использоваться для определения выравнивания. Значение 0 означает, что следует использовать обычное выравнивание. Компилятор также может игнорировать этот параметр.
© Free Software Foundation
Licensed under the GNU Free Documentation License, Version 1.3.
https://gcc.gnu.org/onlinedocs/gcc-15.3.0/gcc/_005f_005fatomic-Builtins.html