Почему std :: atomic <T> :: is_lock_free () не является статичным, как constexpr?

9

Может кто-нибудь сказать мне, является ли std :: atomic :: is_lock_free () не статичным, а также constexpr? Не иметь смысла в том, чтобы он был нестатичным и / или неконстекстовым.

Бонита Монтеро
источник
3
Вы в курсе is_always_lock_free?
Майк ван Дайк
3
Я собираюсь бросить "выравнивание" там.
Макс
@MaxLanghof Вы имеете в виду, что не все экземпляры будут выровнены одинаково?
любопытный парень
1
Майк, нет, я не знал, но спасибо за эту подсказку; это действительно полезно для меня. Но я спрашиваю себя, почему существует решение между is_lock_free () и is_always_lock_free. Это не может быть из-за не выровненной атомики, как предлагали здесь другие, так как язык определяет невыровненный доступ, чтобы в любом случае иметь неопределенное поведение.
Бонита Монтеро

Ответы:

10

Как объяснено на 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 ниже!

Макс Лангоф
источник
1
32-битный x86 - хороший пример того, где вы можете найти ABI 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 ++) все еще имеет эту ошибку!)
Питер Кордес
2
Но правильная реализация C ++ 20 std::atomic_ref<double>либо полностью отклонит заниженное выравнивание double, либо проверит выравнивание во время выполнения на платформах, где это допустимо для простого doubleи int64_tбудет меньше, чем естественное выравнивание. (Потому что atomic_ref<T>действует на объект, который был объявлен как простой T, и имеет только минимальное выравнивание alignof(T)без возможности дать ему дополнительное выравнивание.)
Питер Кордес
2
См. Gcc.gnu.org/bugzilla/show_bug.cgi?id=62259 для исправления исправленной ошибки libstdc ++ и gcc.gnu.org/bugzilla/show_bug.cgi?id=65146 для исправления ошибки C, включая ошибку тестовый пример чистого ISO C11, который показывает разрыв _Atomic int64_tпри компиляции с током gcc -m32. В любом случае, я хочу сказать, что настоящие компиляторы не поддерживают недооцененные атомики и не выполняют проверки во время выполнения (пока?), Поэтому #pragma packили __attribute__((packed))просто приведут к неатомарности; объекты все равно сообщат, что они есть lock_free.
Питер Кордес
1
Но да, цель is_lock_free()состоит в том, чтобы позволить реализациям работать не так, как на самом деле; с проверками времени выполнения, основанными на фактическом выравнивании, для использования атомарных инструкций, поддерживаемых HW, или для использования блокировки.
Питер Кордес
3

Вы можете использовать std::is_always_lock_free

is_lock_free зависит от конкретной системы и не может быть определена во время компиляции.

Соответствующее объяснение:

Атомарные типы также могут иногда быть свободными от блокировки, например, если только выровненные обращения к памяти естественным образом атомарны в данной архитектуре, неправильно выровненные объекты одного типа должны использовать блокировки.

darune
источник
1
std::numeric_limits<int>::maxзависит от архитектуры, но является статичным и constexpr. Я думаю, в этом нет ничего плохого, но я не покупаю первую часть рассуждений
idclev 463035818
1
Не определяет ли доступ к языку без выравнивания поведение в любом случае, так что оценка отсутствия блокировки или нет во время выполнения была бы бессмысленной?
Бонита Монтеро
1
Нет смысла выбирать между выравниваемым и не выровненным доступом, поскольку язык определяет последнее как неопределенное поведение.
Бонита Монтеро
@BonitaMontero Существует смысл «не выровненный в выравнивании объектов C ++» и «не выровненный в том, что нравится аппаратному обеспечению». Это не обязательно то же самое, но на практике они часто бывают. Пример, который вы демонстрируете, является одним из таких случаев, когда компилятор, по-видимому, имеет встроенное предположение, что оба они одинаковы, что означает только то, что на этом компиляторе нетis_lock_free смысла .
Макс
1
Вы можете быть уверены, что атом будет иметь правильное выравнивание, если есть требование выравнивания.
Бонита Монтеро
1

Я установил 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()всегда верно.)

Бонита Монтеро
источник
1
Да, большинство реализаций предпочитают давать, 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 существует, поэтому другие компиляторы могут делать разные вещи.
Макс
1
Вы можете быть совершенно уверены, что если язык говорит о том, что доступ без выравнивания имеет неопределенное поведение, все атомы должны быть правильно выровнены. Из-за этого ни одна реализация не выполнит никаких проверок во время выполнения.
Бонита Монтеро
@BonitaMontero Да, но в языке нет ничего, что бы запрещало alignof(std::atomic<double>) == 1(так что не было бы никакого «неприровненного доступа» в смысле C ++, следовательно, нет UB), даже если аппаратное обеспечение может гарантировать только атомарные операции без блокировки для doubles на 4 или 8-байтовые границы. Затем компилятору придется использовать блокировки в невыровненных случаях (и возвращать соответствующее логическое значение из is_lock_free, в зависимости от расположения в памяти экземпляра объекта).
Макс Лангхоф