Может кто-нибудь сказать мне, является ли std :: atomic :: is_lock_free () не статичным, а также constexpr? Не иметь смысла в том, чтобы он был нестатичным и / или неконстекстовым.
Может кто-нибудь сказать мне, является ли std :: atomic :: is_lock_free () не статичным, а также constexpr? Не иметь смысла в том, чтобы он был нестатичным и / или неконстекстовым.
Ответы:
Как объяснено на cppreference :
Все атомарные типы, за исключением std :: atomic_flag, могут быть реализованы с использованием мьютексов или других операций блокировки, а не с помощью инструкций атомарного процессора без блокировки. Атомарные типы также могут быть иногда свободными от блокировки, например, если только выровненные обращения к памяти естественным образом атомарны в данной архитектуре, неправильно выровненные объекты одного типа должны использовать блокировки.
Стандарт C ++ рекомендует (но не требует), чтобы атомарные операции без блокировки также были безадресными, то есть подходящими для связи между процессами, использующими разделяемую память.
Как уже упоминалось несколькими другими, std::is_always_lock_free
может быть то, что вы действительно ищете.
Редактировать: Чтобы уточнить, типы объектов C ++ имеют значение выравнивания, которое ограничивает адреса их экземпляров только определенными кратными степенями two ( [basic.align]
). Эти значения выравнивания определяются реализацией для основных типов и не должны равняться размеру типа. Они также могут быть более строгими, чем то, что на самом деле может поддерживать оборудование.
Например, x86 (в основном) поддерживает невыровненный доступ. Тем не менее, вы найдете большинство компиляторов alignof(double) == sizeof(double) == 8
для x86, поскольку невыровненный доступ имеет множество недостатков (скорость, кэширование, атомарность ...). Но, например, #pragma pack(1) struct X { char a; double b; };
или alignas(1) double x;
позволяет вам иметь «не выровненные» double
с. Поэтому, когда cppreference говорит о «выровненных обращениях к памяти», он, по-видимому, делает это с точки зрения естественного выравнивания типа для аппаратного обеспечения, не используя тип C ++ таким образом, который противоречит его требованиям к выравниванию (которое будет UB).
Вот дополнительная информация: Каков реальный эффект от успешного доступа без выравнивания на x86?
Пожалуйста, ознакомьтесь с проницательными комментариями @Peter Cordes ниже!
alignof(double)==4
. Но std::atomic<double>
все равно alignof() = 8
вместо проверки выравнивания во время выполнения. Использование упакованной структуры, которая выравнивает атомарные нити, нарушает ABI и не поддерживается. (GCC для 32-битных x86 предпочитает естественное выравнивание 8-байтовых объектов, но правила упаковки структуры переопределяют это и основаны только на них alignof(T)
, например, на i386 System V. В G ++ раньше была ошибка, когда atomic<int64_t>
внутри структуры не было бы атомарной потому что это только предполагалось. GCC (для C не C ++) все еще имеет эту ошибку!)
std::atomic_ref<double>
либо полностью отклонит заниженное выравнивание double
, либо проверит выравнивание во время выполнения на платформах, где это допустимо для простого double
и int64_t
будет меньше, чем естественное выравнивание. (Потому что atomic_ref<T>
действует на объект, который был объявлен как простой T
, и имеет только минимальное выравнивание alignof(T)
без возможности дать ему дополнительное выравнивание.)
_Atomic int64_t
при компиляции с током gcc -m32
. В любом случае, я хочу сказать, что настоящие компиляторы не поддерживают недооцененные атомики и не выполняют проверки во время выполнения (пока?), Поэтому #pragma pack
или __attribute__((packed))
просто приведут к неатомарности; объекты все равно сообщат, что они есть lock_free
.
is_lock_free()
состоит в том, чтобы позволить реализациям работать не так, как на самом деле; с проверками времени выполнения, основанными на фактическом выравнивании, для использования атомарных инструкций, поддерживаемых HW, или для использования блокировки.
Вы можете использовать std::is_always_lock_free
is_lock_free
зависит от конкретной системы и не может быть определена во время компиляции.
Соответствующее объяснение:
Атомарные типы также могут иногда быть свободными от блокировки, например, если только выровненные обращения к памяти естественным образом атомарны в данной архитектуре, неправильно выровненные объекты одного типа должны использовать блокировки.
std::numeric_limits<int>::max
зависит от архитектуры, но является статичным и constexpr
. Я думаю, в этом нет ничего плохого, но я не покупаю первую часть рассуждений
is_lock_free
смысла .
Я установил Visual Studio 2019 на свой Windows-ПК, и у этого устройства также есть ARMv8-компилятор. ARMv8 допускает не выровненный доступ, но сравнение и обмен, заблокированные добавления и т. Д. Должны быть выровнены. Кроме того, чистая загрузка / чистое хранение с использованием ldp
или stp
(пара загрузки или пара хранения 32-разрядных регистров) гарантированно будут атомарными, только если они естественным образом выровнены.
Поэтому я написал небольшую программу для проверки того, что is_lock_free () возвращает для произвольного атомного указателя. Итак, вот код:
#include <atomic>
#include <cstddef>
using namespace std;
bool isLockFreeAtomic( atomic<uint64_t> *a64 )
{
return a64->is_lock_free();
}
И это разборка isLockFreeAtomic
|?isLockFreeAtomic@@YA_NPAU?$atomic@_K@std@@@Z| PROC
movs r0,#1
bx lr
ENDP
Это просто returns true
, ака 1
.
Эта реализация выбирает для использования, alignof( atomic<int64_t> ) == 8
так что каждый atomic<int64_t>
правильно выровнен. Это исключает необходимость проверки выравнивания во время выполнения при каждой загрузке и хранении.
(Примечание редактора: это часто встречается; большинство реальных реализаций C ++ работают именно так. Вот почему std::is_always_lock_free
это так полезно: потому что это обычно верно для типов, где is_lock_free()
всегда верно.)
atomic<uint64_t>
и alignof() == 8
поэтому им не нужно проверять выравнивание во время выполнения. Этот старый API дает им возможность не делать этого, но на текущем HW имеет гораздо больше смысла просто требовать выравнивания (в противном случае UB, например, не атомарность). Даже в 32-битном коде, где int64_t
может быть только 4-байтовое выравнивание, atomic<int64_t>
требуется 8-байтовый. Смотрите мои комментарии к другому ответу
alignof
значение для базового типа таким же, как «хорошее» выравнивание аппаратного обеспечения, то is_lock_free
всегда будет true
(и так будет is_always_lock_free
). Ваш компилятор здесь делает именно это. Но API существует, поэтому другие компиляторы могут делать разные вещи.
alignof(std::atomic<double>) == 1
(так что не было бы никакого «неприровненного доступа» в смысле C ++, следовательно, нет UB), даже если аппаратное обеспечение может гарантировать только атомарные операции без блокировки для double
s на 4 или 8-байтовые границы. Затем компилятору придется использовать блокировки в невыровненных случаях (и возвращать соответствующее логическое значение из is_lock_free
, в зависимости от расположения в памяти экземпляра объекта).
is_always_lock_free
?