A frase "fortemente acontece antes" é usada várias vezes no rascunho do padrão C ++.
Por exemplo: Rescisão [basic.start.term] / 5
Se a conclusão da inicialização de um objeto com duração de armazenamento estático ocorrer fortemente antes de uma chamada para std :: atexit (consulte [support.start.term]), a chamada para a função transmitida para std :: atexit é sequenciado antes da chamada para o destruidor do objeto. Se uma chamada para std :: atexit ocorrer fortemente antes da conclusão da inicialização de um objeto com duração de armazenamento estático, a chamada para o destruidor do objeto será sequenciada antes da chamada para a função passada para std :: atexit . Se uma chamada para std :: atexit acontecer fortemente antes de outra chamada para std :: atexit, a chamada para a função passada para a segunda chamada std :: atexit será sequenciada antes da chamada para a função passada para o primeira chamada std :: atexit.
E definido em Corridas de dados [intro.races] / 12
Uma avaliação A ocorre fortemente antes de uma avaliação D se,
(12.1) A é sequenciado antes de D, ou
(12.2) A sincroniza com D, e ambos A e D são operações atômicas sequencialmente consistentes ([atomics.order]), ou
(12.3) existem avaliações B e C, de modo que A é sequenciado antes de B, B simplesmente acontece antes de C e C é sequenciado antes de D, ou
(12.4) há uma avaliação B, de modo que A ocorre fortemente antes de B e B ocorre fortemente antes de D.
[Nota: Informalmente, se A acontecer fortemente antes de B, então A parecerá ser avaliado antes de B em todos os contextos. Fortemente acontece antes de excluir operações de consumo. - nota final]
Por que "fortemente aconteceu antes" foi introduzido? Intuitivamente, qual é a diferença e a relação com "acontece antes"?
O que significa "A parece ser avaliado antes de B em todos os contextos" na nota?
(Nota: a motivação para esta pergunta são os comentários de Peter Cordes sob esta resposta .)
Projeto adicional de cotação padrão (graças a Peter Cordes)
Ordem e consistência [atomics.order] / 4
Existe uma única ordem total S em todas as operações memory_order :: seq_cst, incluindo cercas, que atendem às seguintes restrições. Primeiro, se A e B são operações memory_order :: seq_cst e A ocorre fortemente antes de B, então A precede B em S. Segundo, para cada par de operações atômicas A e B em um objeto M, onde A é ordenado por coerência antes de B, as quatro condições a seguir devem ser atendidas por S:
(4.1) se A e B são ambas operações memory_order :: seq_cst, então A precede B em S; e
(4.2) se A é uma operação memory_order :: seq_cst e B ocorre antes de uma memory_order :: seq_cst cerca Y, então A precede Y em S; e
(4.3) se um memory_order :: seq_cst fence X acontecer antes de A e B ser uma operação memory_order :: seq_cst, X precederá B em S; e
(4.4) se um memory_order :: seq_cst fence X acontecer antes de A e B acontecer antes de um memory_order :: seq_cst fence Y, então X precederá Y em S.
fonte
seq_cst
, no Atomics 31.4 Ordem e consistência: 4 . Isso não está no padrão C ++ 17 n4659 , onde 32.4 - 3 define a existência de uma única ordem total de operações seq_cst consistentes com a ordem "acontece antes" e as ordens de modificação para todos os locais afetados ; o "fortemente" foi adicionado em um rascunho posterior.atexit()
um thread eexit()
outro, não é suficiente que os inicializadores carreguem apenas uma dependência baseada em consumo, porque os resultados diferem se foremexit()
invocados pelo mesmo thread. Uma resposta minha mais antiga dizia respeito a essa diferença.exit()
. Qualquer encadeamento pode matar o programa inteiro saindo, ou o encadeamento principal pode sairreturn
pressionando -ing. Isso resulta na chamada deatexit()
manipuladores e na morte de todos os threads, independentemente do que eles estavam fazendo.Respostas:
Prepare-se para "simplesmente acontece antes" também! Dê uma olhada neste instantâneo atual do cppref https://en.cppreference.com/w/cpp/atomic/memory_order
Parece que "simplesmente acontece antes" é adicionado no C ++ 20.
Portanto, Simply-HB e HB são os mesmos, exceto pelo modo como lidam com as operações de consumo. Veja HB
Como eles diferem em relação ao consumo? Consulte Inter-Thread-HB
Uma operação ordenada por dependência (ou seja, usa liberação / consumo) é HB, mas não necessariamente Simply-HB.
Consumir é mais descontraído do que adquirir, então, se eu entendi direito, o HB é mais descontraído do que o Simply-HB.
Portanto, uma operação de liberação / consumo não pode ser Strongly-HB.
Liberar / adquirir pode ser HB e Simply-HB (porque liberar / adquirir sincroniza com), mas não é necessariamente Strongly-HB. Porque o Strongly-HB diz especificamente que A deve sincronizar-se com B E ser uma operação Sequencialmente Consistente.
Todos os contextos: todos os threads / todas as CPUs veem (ou "eventualmente concordarão") a mesma ordem. Esta é a garantia de consistência sequencial - uma ordem global de modificação total de todas as variáveis. As cadeias de aquisição / liberação garantem apenas a ordem de modificação percebida para os segmentos que participam da cadeia. Tópicos fora da cadeia são teoricamente permitidos para ver uma ordem diferente.
Não sei por que o Strongly-HB e o Simply-HB foram introduzidos. Talvez para ajudar a esclarecer como operar em torno de consumir? O Strongly-HB possui boas propriedades - se um thread observa A acontece fortemente antes de B, ele sabe que todos os threads irão observar a mesma coisa.
A história do consumo:
Paul E. McKenney é responsável por consumir os padrões C e C ++. Consuma garantias ordenando entre a atribuição do ponteiro e a memória para a qual aponta. Foi inventado por causa do DEC Alpha. O DEC Alpha poderia desreferenciar especulativamente um ponteiro, portanto, também tinha uma cerca de memória para evitar isso. O DEC Alpha não é mais fabricado e nenhum processador hoje possui esse comportamento. Consumir se destina a ser muito relaxado.
fonte
mo_consume
destina-se a tirar proveito da ordem de dependência de dados em CPUs reais e formalizar que o compilador não pode interromper a dependência de dados via previsão de ramificação. por exemplo,int *p = load();
tmp = *p;
poderia ser interrompido pela introdução do compiladorif(p==known_address) tmp = *known_address; else tmp=*p;
se houvesse algum motivo para esperar que um determinado valor de ponteiro fosse comum. Isso é legal para relaxar, mas não para consumir.