Alguém pode me dizer se std :: atomic :: is_lock_free () não é estático, assim como constexpr? Tê-lo não estático e / ou não constexpr não faz sentido para mim.
Alguém pode me dizer se std :: atomic :: is_lock_free () não é estático, assim como constexpr? Tê-lo não estático e / ou não constexpr não faz sentido para mim.
Respostas:
Conforme explicado na cppreference :
Todos os tipos atômicos, exceto std :: atomic_flag, podem ser implementados usando mutexes ou outras operações de bloqueio, em vez de usar as instruções da CPU atômica sem bloqueio. Às vezes, os tipos atômicos também podem ser livres de bloqueios; por exemplo, se apenas os acessos à memória alinhados são naturalmente atômicos em uma determinada arquitetura, objetos desalinhados do mesmo tipo precisam usar bloqueios.
O padrão C ++ recomenda (mas não exige) que operações atômicas sem bloqueio também sejam livres de endereço, ou seja, adequadas para comunicação entre processos usando memória compartilhada.
Conforme mencionado por várias outras pessoas, std::is_always_lock_free
pode ser o que você realmente está procurando.
Editar: para esclarecer, os tipos de objetos C ++ têm um valor de alinhamento que restringe os endereços de suas instâncias a apenas determinados múltiplos de potências de dois ( [basic.align]
). Esses valores de alinhamento são definidos pela implementação para tipos fundamentais e não precisam ser iguais ao tamanho do tipo. Eles também podem ser mais rigorosos do que o que o hardware realmente suporta.
Por exemplo, x86 (principalmente) suporta acessos não alinhados. No entanto, você encontrará a maioria dos compiladores que possuem o alignof(double) == sizeof(double) == 8
x86, pois os acessos desalinhados têm uma série de desvantagens (velocidade, cache, atomicidade ...). Mas, por exemplo, #pragma pack(1) struct X { char a; double b; };
ou alignas(1) double x;
permite que você tenha "desalinhados" double
s. Portanto, quando cppreference fala sobre "acessos de memória alinhados", presumivelmente o faz em termos do alinhamento natural do tipo para o hardware, não usando um tipo C ++ de uma maneira que contradiga seus requisitos de alinhamento (que seria UB).
Aqui estão mais informações: Qual é o efeito real de acessos não alinhados com êxito no x86?
Confira também os comentários perspicazes de @Peter Cordes abaixo!
alignof(double)==4
. Mas std::atomic<double>
ainda tem, em alignof() = 8
vez de verificar o alinhamento em tempo de execução. O uso de uma estrutura empacotada que subalinha o atômico interrompe o ABI e não é suportado. (O GCC para x86 de 32 bits prefere dar um alinhamento natural aos objetos de 8 bytes, mas as regras de empacotamento de estruturas substituem isso e são baseadas apenas alignof(T)
, por exemplo, no i386 System V. O G ++ costumava ter um erro em que atomic<int64_t>
dentro de uma estrutura pode não ser atômico porque ele só assumiu GCC (para C não C ++) ainda tem esse bug).!
std::atomic_ref<double>
rejeitará completamente o sub-alinhamento double
ou verificará o alinhamento no tempo de execução nas plataformas em que é legal double
e simples e int64_t
menos alinhado naturalmente. (Porque atomic_ref<T>
opera sobre um objeto que foi declarada como uma planície T
, e tem apenas uma harmonização mínima de alignof(T)
sem a oportunidade de dar-lhe o alinhamento extra.)
_Atomic int64_t
compilado com corrente gcc -m32
. Enfim, o que quero dizer é que os compiladores reais não suportam átomos sub-alinhados e não fazem verificações em tempo de execução (ainda?); Portanto, #pragma pack
ou __attribute__((packed))
apenas levarão à não-atomicidade; objetos ainda relatam que são lock_free
.
is_lock_free()
é permitir que as implementações funcionem de maneira diferente da atual; com verificações de tempo de execução baseadas no alinhamento real para usar instruções atômicas suportadas por HW ou usar uma trava.
Você pode usar std::is_always_lock_free
is_lock_free
depende do sistema real e não pode ser determinado em tempo de compilação.
Explicação relevante:
Às vezes, os tipos atômicos também podem ser livres de bloqueios, por exemplo, se apenas os acessos alinhados à memória são naturalmente atômicos em uma determinada arquitetura, objetos desalinhados do mesmo tipo precisam usar bloqueios.
std::numeric_limits<int>::max
depende da arquitetura, ainda é estático e constexpr
. Eu acho que não há nada errado na resposta, mas eu não comprar a primeira parte do raciocínio
is_lock_free
faz sentido nesse compilador .
Instalei o Visual Studio 2019 no meu PC com Windows e este devenv também possui um compilador ARMv8. O ARMv8 permite acessos desalinhados, mas a comparação e trocas, adições bloqueadas etc. são obrigatórias para serem alinhadas. E também carga pura / armazenamento puro usando ldp
or stp
(par de carga ou par de registros de 32 bits) só são garantidos como atômicos quando alinhados naturalmente.
Então, escrevi um pequeno programa para verificar o que is_lock_free () retorna para um ponteiro atômico arbitrário. Então aqui está o código:
#include <atomic>
#include <cstddef>
using namespace std;
bool isLockFreeAtomic( atomic<uint64_t> *a64 )
{
return a64->is_lock_free();
}
E essa é a desmontagem do isLockFreeAtomic
|?isLockFreeAtomic@@YA_NPAU?$atomic@_K@std@@@Z| PROC
movs r0,#1
bx lr
ENDP
Isto é apenas returns true
, aka 1
.
Essa implementação escolhe usar alignof( atomic<int64_t> ) == 8
para que todos atomic<int64_t>
estejam alinhados corretamente. Isso evita a necessidade de verificações de alinhamento do tempo de execução em cada carregamento e armazenamento.
(Nota do editor: isso é comum; a maioria das implementações da vida real em C ++ funciona dessa maneira. É por std::is_always_lock_free
isso que é tão útil: porque geralmente é verdade para tipos onde is_lock_free()
é verdade.)
atomic<uint64_t>
e, alignof() == 8
portanto, não precisa verificar o alinhamento no tempo de execução. Essa API antiga oferece a opção de não fazer isso, mas no HW atual, faz muito mais sentido exigir alinhamento (caso contrário, UB, por exemplo, não atomicidade). Mesmo no código de 32 bits, onde int64_t
pode ter apenas alinhamento de 4 bytes, atomic<int64_t>
requer 8 bytes. Veja meus comentários em outra resposta
alignof
valor para um tipo fundamental o mesmo que o "bom" alinhamento do hardware, em seguida, is_lock_free
será sempre true
(e assim vai is_always_lock_free
). Seu compilador aqui faz exatamente isso. Mas a API existe para que outros compiladores possam fazer coisas diferentes.
alignof(std::atomic<double>) == 1
(portanto, não haveria "acesso desalinhado" no sentido C ++, portanto, não UB), mesmo que o hardware possa garantir operações atômicas livres de bloqueio para double
s on 4 ou Limites de 8 bytes. O compilador precisaria usar bloqueios nos casos não alinhados (e retornar o valor booleano apropriado de is_lock_free
, dependendo do local da memória da instância do objeto).
is_always_lock_free
?