Na documentação de std::memory_order
em cppreference.com, há um exemplo de pedido relaxado:
Ordenação descontraída
As operações atômicas marcadas
memory_order_relaxed
não são operações de sincronização; eles não impõem uma ordem entre acessos simultâneos à memória. Eles garantem apenas consistência de ordem de atomicidade e modificação.Por exemplo, com xey inicialmente zero,
// Thread 1: r1 = y.load(std::memory_order_relaxed); // A x.store(r1, std::memory_order_relaxed); // B // Thread 2: r2 = x.load(std::memory_order_relaxed); // C y.store(42, std::memory_order_relaxed); // D
é permitido produzir r1 == r2 == 42 porque, embora A seja sequenciado antes de B no segmento 1 e C seja sequenciado antes de D no segmento 2, nada impede que D apareça antes de A na ordem de modificação de y e B de aparecendo antes de C na ordem de modificação de x. O efeito colateral de D em y pode ser visível para a carga A no encadeamento 1, enquanto o efeito colateral de B em x pode ser visível na carga C no encadeamento 2. Em particular, isso pode ocorrer se D for concluído antes de C em segmento 2, devido a reordenação do compilador ou em tempo de execução.
diz "C é sequenciado antes de D no segmento 2".
De acordo com a definição de sequenciado antes, que pode ser encontrado em Ordem de avaliação , se A for sequenciado antes de B, a avaliação de A será concluída antes do início da avaliação de B. Como C é sequenciado antes de D no encadeamento 2, C deve ser concluído antes do início de D, portanto, a parte da condição da última frase do instantâneo nunca será satisfeita.
fonte
Respostas:
Eu acredito que a preferência é certa. Eu acho que isso se resume à regra "como se" [introdução.execução] / 1 . Compiladores são obrigados a reproduzir apenas o comportamento observável do programa descrito por seu código. Uma relação sequenciada antes é estabelecida apenas entre as avaliações da perspectiva do segmento em que essas avaliações são realizadas [introdução.execução] / 15 . Isso significa que quando duas avaliações sequenciadas uma após a outra aparecem em algum lugar de algum encadeamento, o código realmente em execução nesse encadeamento deve se comportar como se o que quer que a primeira avaliação fizesse realmente afetar o que a segunda avaliação faz. Por exemplo
deve imprimir 42. No entanto, o compilador não precisa realmente armazenar o valor 42 em um objeto
x
antes de ler o valor novamente desse objeto para imprimi-lo. É bom lembrar que o último valor a ser armazenadox
foi 42 e, em seguida, basta imprimir o valor 42 diretamente antes de fazer um armazenamento real do valor 42 emx
. De fato, sex
for uma variável local, ela também pode apenas rastrear qual valor foi atribuído pela última vez a qualquer momento e nunca criar um objeto ou realmente armazenar o valor 42. Não há como o thread diferenciar. O comportamento sempre será como se houvesse uma variável e como se o valor 42 estivesse realmente armazenado em um objetox
antessendo carregado desse objeto. Mas isso não significa que o código de máquina gerado tenha que realmente armazenar e carregar qualquer coisa em qualquer lugar. Tudo o que é necessário é que o comportamento observável do código de máquina gerado seja indistinguível do que seria o comportamento se todas essas coisas realmente acontecessem.Se olharmos para
então sim, C é sequenciado antes de D. Mas quando visto a partir desse encadeamento isoladamente, nada que C faça afeta o resultado de D. E nada que D faça alteraria o resultado de C. A única maneira de um afetar o outro seria como uma conseqüência indireta de algo acontecendo em outro segmento. No entanto, especificando
std::memory_order_relaxed
, você declarou explicitamenteque a ordem na qual a carga e a loja são observadas por outro encadeamento é irrelevante. Como nenhum outro encadeamento pode observar a carga e o armazenamento em qualquer ordem específica, não há nada que outro encadeamento possa fazer para que C e D se afetem de maneira consistente. Portanto, a ordem na qual a carga e a loja são realmente executadas é irrelevante. Assim, o compilador é livre para reordená-los. E, como mencionado na explicação abaixo desse exemplo, se o armazenamento de D for realizado antes da carga de C, então r1 == r2 == 42 pode realmente acontecer…fonte
Às vezes, é possível que uma ação seja ordenada em relação a duas outras seqüências de ações, sem implicar em nenhuma ordem relativa das ações nessas seqüências em relação uma à outra.
Suponha, por exemplo, que um tenha os três eventos a seguir:
e a leitura de p2 é ordenada independentemente após a gravação de p1 e antes da gravação de p3, mas não há uma ordem específica na qual ambos p1 e p3 participam. Dependendo do que é feito com p2, pode ser impraticável para um compilador adiar p1 além de p3 e ainda atingir a semântica necessária com p2. Suponha, no entanto, que o compilador soubesse que o código acima fazia parte de uma sequência maior:
Nesse caso, ele poderia determinar que poderia reordenar o armazenamento para p1 após o código acima e consolidá-lo com o seguinte armazenamento, resultando em código que grava p3 sem escrever p1 primeiro:
Embora possa parecer que as dependências de dados possam fazer com que certas partes das relações de sequenciamento se comportem de forma transitória, um compilador pode identificar situações em que não existem dependências aparentes de dados e, portanto, não teria os efeitos transitivos que se esperaria.
fonte
Se houver duas instruções, o compilador gerará código em ordem seqüencial, portanto, o código para a primeira será colocado antes da segunda. Mas a cpus possui internamente pipelines e é capaz de executar operações de montagem em paralelo. A instrução C é uma instrução de carregamento. Enquanto a memória está sendo buscada, o pipeline processará as próximas instruções e, como elas não dependem da instrução de carregamento, elas poderão ser executadas antes da conclusão de C (por exemplo, dados para D estavam no cache, C na memória principal).
Se o usuário realmente precisou que as duas instruções fossem executadas seqüencialmente, operações mais rigorosas de ordenação de memória podem ser usadas. Em geral, os usuários não se importam desde que o programa esteja logicamente correto.
fonte
Tudo o que você pensa é igualmente válido. O padrão não diz o que é executado sequencialmente, o que não é e como ele pode ser misturado .
Cabe a você e a todos os programadores criar uma semântica consistente sobre essa bagunça, um trabalho digno de vários doutores.
fonte