As threads A, B e C realizam trabalhos separados (não é necessária sincronização entre elas). Assim que as três forem concluídas, a thread D combinará seus resultados. Portanto, D depende da conclusão de A, B e C.
int a = 0;
int b = 0;
int c = 0;
std::atomic_int D_dependencies{ 3 };
linha A:
a = 1;
D_dependencies.fetch_sub(1, std::memory_order_release);
linha B:
b = 1;
D_dependencies.fetch_sub(1, std::memory_order_release);
linha C:
c = 1;
D_dependencies.fetch_sub(1, std::memory_order_release);
linha D:
if(D_dependencies.load(std::memory_order_acquire) == 0)
{
assert(a + b + c == 3);
}
Meu entendimento é que as operações RMW fetch_sub
formam uma "sequência de liberação" e, portanto, o carregamento na thread D deve observar todas as gravações se carregar 0 da variável atômica.
Estou correto?
Sim, está correto.
Há três sequências de liberação sobrepostas, de modo que a aquisição-carga sincroniza com todos os três RMWs de liberação. Os RMWs incluem,
release
para que cada um possa liderar sua própria sequência de liberação, além de fazer parte de uma sequência mais longa. (acq_rel
ouseq_cst
também incluirrelease
e funcionaria aqui.)As garantias no padrão se aplicam a todos os casos em que as condições se aplicam - liberação de armazenamento (inclusive como parte de um RMW), zero ou mais operações RMW intervenientes (de qualquer
memory_order
), então uma operação de aquisição sincroniza com a operação de liberação original da qual viu um valor (ou um valor dependente dela por meio de uma cadeia de RMWs).No formalismo do padrão, cada
release
operação lidera sua própria sequência de liberação e, portanto, você pode ter sequências de liberação sobrepostas. (Eu acho; não verifiquei duas vezes o texto do padrão.)Também funciona pensar em uma cadeia de RMWs como uma sequência de liberação e
acquire
operações sincronizadas com todasrelease
as operações -ou-mais fortes na cadeia.Uma loja pura quebra uma sequência de liberação , mas você não tem essas em
D_dependencies
.Relacionado:
Sim, e você também pode usar std::counting_semaphore , onde você pode usar release() / e try_acquire() .
A execução do programa acima produz esta saída:
Assim, o Thread D (thread consumidor) pode fazer outro trabalho de forma independente e verificar periodicamente se outros threads produtores (A, B e C) concluíram seu trabalho.