Czy ktoś może mi powiedzieć, czy std :: atomic :: is_lock_free () nie jest statyczny tak dobrze jak constexpr? Posiadanie go jako niestatycznego i / lub jako non-constexpr nie ma dla mnie sensu.
Czy ktoś może mi powiedzieć, czy std :: atomic :: is_lock_free () nie jest statyczny tak dobrze jak constexpr? Posiadanie go jako niestatycznego i / lub jako non-constexpr nie ma dla mnie sensu.
Odpowiedzi:
Jak wyjaśniono na cppreference :
Wszystkie typy atomowe z wyjątkiem std :: atomic_flag mogą być implementowane przy użyciu muteksów lub innych operacji blokowania, zamiast instrukcji atomowych bez blokowania instrukcji. Typy atomowe mogą być czasami wolne od blokowania, np. Jeśli tylko wyrównane dostępy do pamięci są naturalnie atomowe w danej architekturze, niedopasowane obiekty tego samego typu muszą używać blokad.
Standard C ++ zaleca (ale nie wymaga), aby operacje atomowe bez blokowania były również pozbawione adresu, czyli odpowiednie do komunikacji między procesami wykorzystującymi pamięć współdzieloną.
Jak wspomniano przez wiele innych, std::is_always_lock_free
może być tym, czego naprawdę szukasz.
Edycja: Aby wyjaśnić, typy obiektów C ++ mają wartość wyrównania, która ogranicza adresy ich instancji do tylko niektórych wielokrotności potęg dwóch ( [basic.align]
). Te wartości wyrównania są zdefiniowane w implementacji dla typów podstawowych i nie muszą być równe wielkości typu. Mogą być również bardziej rygorystyczne niż to, co sprzęt może faktycznie obsługiwać.
Na przykład x86 (głównie) obsługuje niezrównane dostępy. Znajdziesz jednak większość kompilatorów mających alignof(double) == sizeof(double) == 8
x86, ponieważ nieprzypisane dostępy mają wiele wad (szybkość, buforowanie, atomowość ...). Ale np #pragma pack(1) struct X { char a; double b; };
lub alignas(1) double x;
pozwala mieć „niewyrównany” double
s. Kiedy więc cppreference mówi o „wyrównanym dostępie do pamięci”, przypuszczalnie robi to w kategoriach naturalnego wyrównania typu dla sprzętu, nie używając typu C ++ w sposób sprzeczny z jego wymaganiami dotyczącymi wyrównania (którym byłby UB).
Oto więcej informacji: Jaki jest rzeczywisty wpływ udanego niezaangażowanego dostępu na x86?
Sprawdź także wnikliwe komentarze @Peter Cordes poniżej!
alignof(double)==4
. Ale std::atomic<double>
nadal ma alignof() = 8
zamiast sprawdzania wyrównania w czasie wykonywania. Użycie upakowanej struktury, która nie wyrównuje atomów, psuje ABI i nie jest obsługiwane. (GCC dla 32-bitowego x86 woli nadawać obiektom 8-bajtowym naturalne wyrównanie, ale reguły pakowania struktur zastępują to i są oparte tylko na alignof(T)
, np. Na i386 System V. G ++ miał błąd, w którym atomic<int64_t>
wewnątrz struktury może nie być atomowy bo to tylko założenie. GCC (dla C nie C ++) wciąż ma ten błąd!)
std::atomic_ref<double>
albo double
całkowicie odrzuci niedostosowanie , albo sprawdzi wyrównanie w środowisku wykonawczym na platformach, na których jest to zgodne z prawem double
i int64_t
jest mniej niż naturalnie wyrównane. (Ponieważ atomic_ref<T>
działa na obiekcie, który został zadeklarowany jako zwykły T
i ma tylko minimalne wyrównanie alignof(T)
bez możliwości nadania mu dodatkowego wyrównania.)
_Atomic int64_t
po skompilowaniu z prądem gcc -m32
. W każdym razie, chodzi mi o to, że realne kompilatory nie obsługują niedostatecznie wyrównanych Atomics i nie robić kontrole uruchomieniowe (jeszcze?), Więc #pragma pack
albo __attribute__((packed))
będzie tylko prowadzić do nieprzestrzegania atomowości; obiekty nadal zgłaszają, że są lock_free
.
is_lock_free()
jest umożliwienie implementacjom działania innego niż w rzeczywistości. z kontrolami wykonawczymi opartymi na faktycznym wyrównaniu w celu użycia instrukcji atomowych obsługiwanych przez HW lub blokady.
Możesz użyć std::is_always_lock_free
is_lock_free
zależy od rzeczywistego systemu i nie można go ustalić w czasie kompilacji.
Odpowiednie wyjaśnienie:
Typy atomowe mogą być czasami wolne od blokowania, np. Jeśli tylko wyrównane dostępy do pamięci są naturalnie atomowe w danej architekturze, niedopasowane obiekty tego samego typu muszą używać blokad.
std::numeric_limits<int>::max
zależy od architektury, ale jest statyczny i constexpr
.
is_lock_free
ma sensu w tym kompilatorze .
Mam zainstalowany program Visual Studio 2019 na moim komputerze z systemem Windows, a to devenv ma również kompilator ARMv8. ARMv8 pozwala na niezrównany dostęp, ale porównywanie i zamiana, zablokowane dodawanie itp. Są obowiązkowe do wyrównania. A także czyste ładowanie / przechowywanie za pomocą ldp
lub stp
(para ładująca lub para 32-bitowych rejestrów) gwarantuje, że będą atomowe tylko wtedy, gdy są naturalnie wyrównane.
Napisałem więc mały program, aby sprawdzić, co is_lock_free () zwraca dla dowolnego wskaźnika atomowego. Oto kod:
#include <atomic>
#include <cstddef>
using namespace std;
bool isLockFreeAtomic( atomic<uint64_t> *a64 )
{
return a64->is_lock_free();
}
I to jest demontaż isLockFreeAtomic
|?isLockFreeAtomic@@YA_NPAU?$atomic@_K@std@@@Z| PROC
movs r0,#1
bx lr
ENDP
To tylko returns true
aka 1
.
Ta implementacja decyduje się na użycie, alignof( atomic<int64_t> ) == 8
więc każda atomic<int64_t>
jest poprawnie wyrównana. Pozwala to uniknąć konieczności sprawdzania wyrównania środowiska wykonawczego dla każdego ładunku i magazynu.
(Uwaga redaktora: jest to powszechne; większość rzeczywistych implementacji C ++ działa w ten sposób. Dlatego std::is_always_lock_free
jest tak przydatna: ponieważ zazwyczaj jest prawdziwa dla typów, w których is_lock_free()
zawsze jest prawdziwa.)
atomic<uint64_t>
, a alignof() == 8
więc nie trzeba sprawdzić ustawienie na starcie. Ten stary interfejs API daje im opcję, aby tego nie robić, ale na obecnym sprzęcie komputerowym sensowniej jest po prostu wymagać wyrównania (w przeciwnym razie UB, np. Brak atomowości). Nawet w 32-bitowym kodzie, w którym int64_t
wyrównanie tylko 4-bajtowe atomic<int64_t>
wymaga 8-bajtowego wyrównania . Zobacz moje komentarze do innej odpowiedzi
alignof
wartość dla typu podstawowego taką samą jak „dobre” wyrównanie sprzętu, to is_lock_free
zawsze będzie true
(i tak też będzie is_always_lock_free
). Twój kompilator tutaj właśnie to robi. Ale interfejs API istnieje, więc inne kompilatory mogą robić różne rzeczy.
alignof(std::atomic<double>) == 1
(więc nie byłoby „niezaangażowanego dostępu” w sensie C ++, a więc bez UB), nawet jeśli sprzęt może zagwarantować jedynie blokadowe operacje atomowe dla double
s na 4 lub 8 bajtów granic. Kompilator musiałby wówczas użyć blokad w niezrównanych przypadkach (i zwrócić odpowiednią wartość logiczną z is_lock_free
, w zależności od lokalizacji pamięci wystąpienia obiektu).
is_always_lock_free
?