Я ищу альтернативу std::atomic_ref
, которая может быть используется без поддержки C ++ 20. Я рассматривал приведение указателей на std::atomic
, но это не кажется безопасным вариантом.
Сценарий использования будет заключаться в применении атомарных операций к неатомарным объектам на время существования атомарной ссылки, чтобы избежать гонки. Доступные объекты не могут быть все атомарными, поэтому потребуется оболочка, такая как atomic_ref.
Любая помощь приветствуется!
2 ответа
Если ваш компилятор поддерживает OpenMP (большинство из них поддерживает), вы можете пометить доступ к вашему объекту с помощью #pragma atomic
. Возможно, с правильной операцией (read
, write
, update
, capture
) и семантикой упорядочения памяти.
ИЗМЕНИТЬ
Кроме того, похоже, что Boost предоставляет atomic_ref
, доступный для кодов до C ++ 20: https://www.boost.org/doc/libs/1_75_0/doc/html/atomic/interface.html#atomic.interface.interface_atomic_ref а>
Другой способ - преобразование неатомарного объекта в атомарный с помощью reinterpret_cast
. Это решение, скорее всего, вызовет неопределенное поведение, но на самом деле может работать с некоторыми реализациями. Например, он используется в библиотеке Facebook Folly: https://github.com/facebook/folly/blob/master/folly/synchronization/PicoSpinLock.h#L95.
В GCC / clang (и других компиляторах, реализующих расширения GNU C) вы можете использовать __atomic
встроенные функции, например
int load_result = __atomic_load_n(&plain_int_var, __ATOMIC_ACQUIRE);
Вот как реализован atomic_ref<T>
на таких компиляторах: просто оболочки для этих встроенных команд. (Вот почему atomic_ref
очень легкий, и обычно лучше конструировать его бесплатно каждый раз, когда он вам нужен, а не хранить один atomic_ref
.)
Поскольку у вас не будет std::atomic_ref<T>::required_alignment
, обычно достаточно, чтобы обеспечить естественное выравнивание объектов, т. е. alignas( sizeof(T) ) T foo;
убедиться, что операции __atomic
действительно атомарны, а также имеют гарантии порядка памяти. (Во многих реализациях все простые T
, которые вообще поддерживают безблокировочную атомику, уже получают достаточное выравнивание, но, например, некоторые 32-битные системы выравнивают int64_t
только по 4 байтам, но 8-байтовые атомики являются только атомарный с 8-байтовым выравниванием. x86 gcc -m32
имел проблему с этим на C ++ на некоторое время, а для намного дольше с _Atomic
в C, окончательно исправлено в 2020 году, хотя затронуло только члены структуры.)
reinterpret_cast< std::atomic<T>* >
на практике может работать на большинстве компиляторов , возможно, даже не будучи UB, в зависимости от внутреннего устройства atomic<>
.
(Большинство?) Другие компиляторы реализуют атомарный (и атомарный_ref) способом, похожим на GNU C, я думаю, используя встроенные функции. например для MSVC, что-то вроде _InterlockedExchange()
для реализации atomic<>::exchange
.
В основных реализациях C ++ atomic<T>
имеет тот же размер и макет, что и обычный T
. (Размер - это то, что вы можете static_assert
). Теоретически для неблокирующего atomic<>
можно включать мьютекс или что-то в этом роде, но в обычных реализациях этого не делается (Где блокировка для std :: atomic?). (Частично для совместимости с C11 _Atomic
, который IIRC предъявляет некоторые требования к тому, чтобы даже неинициализированные или, возможно, инициализированные нулем объекты все еще работали должным образом. Но также только по причинам размера.)
Несмотря на то, что ISO C ++ не гарантирует, что он четко определен, вы в конечном итоге вызовете __atomic_fetch_add_n
или InterlockedAdd
для переменной-члена int
atomic<int>
с тем же адресом, что и исходный простой int
.
Технически это все еще может быть UB; есть правило о совместимости структур до первого различия в их определении, но я менее уверен насчет int*
в структуре или особенно указателя struct{int;}*
на объект int
. Я думаю, что это нарушает правило строгого псевдонима.
Но думаю все равно вряд ли пробьется на практике. Тем не менее, возможный сбой будет обнаружен только при оптимизации и будет зависеть от окружающего кода, а это означает, что вы не можете легко написать модульный тест.
Однако наиболее вероятным сценарием поломки будет ситуация, когда та же функция (после встраивания) считывала или записывала простую переменную в сочетании с операциями с той же переменной через atomic<>*
или { {X1}} ссылка. Особенно, если нет какого-либо барьера памяти, разделяющего эти обращения, например вызова some_thread.join()
. Если вы смешиваете атомарный и неатомарный доступ в одной функции (после встраивания), это может быть безопасным и достаточно переносимым, чтобы работать до тех пор, пока вы не сможете правильно использовать atomic_ref<>
.
Другой хороший краткосрочный вариант - вручную напрямую использовать атомарные встроенные модули GNU C или MSVC, если ваш исходный код в настоящее время заботится только об одном или другом. Или используйте собственный (ограниченное подмножество) atomic_ref
, используя те версии этих функций, которые вам действительно нужны.
- https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html
_InterlockedAdd
(внутренняя / встроенная функция) https://docs.microsoft.com/en-us/previous-versions/51s265a6 (v = vs.85)InterlockedAdd
(функция библиотеки Windows) https://docs.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-interlockedadd
Похожие вопросы
Связанные вопросы
Новые вопросы
c++
C ++ - это язык программирования общего назначения. Первоначально он был разработан как расширение C и имеет аналогичный синтаксис, но теперь это совершенно другой язык. Используйте этот тег для вопросов о коде (который должен быть) скомпилирован с помощью компилятора C ++. Используйте тег для конкретной версии для вопросов, связанных с конкретной версией стандарта [C ++ 11], [C ++ 14], [C ++ 17], [C ++ 20] или [C ++ 23] и т. Д. .