Estou com dificuldades para entender a ordem que está realmente acontecendo para fazer com que esse teste ocasionalmente entre em deadlock. Peço desculpas pelo exemplo ser um pouco longo, foi difícil reproduzir meu problema de forma confiável.
O ideal é que
inc_thread
os incrementos data
então definam flag
e notifiquem dec_thread
.
dec_thread
aguarda para flag
ser definido enquanto data
for diferente de zero, então decrementa data
e limpa flag
.
Isso se repete REP_COUNT
vezes para cada thread com data
finalização em 0
.
Se flag.store(false, mem_order);
usar std::memory_order_acq_rel
isso ocasionalmente causará deadlock, com dec_thread
espera em flag
while data
diferente de zero. Por quê?
Isso acontece notavelmente mesmo quando todas as outras operações são implícita seq_cst
ou explicitamente acq_rel
.
#include <atomic>
#include <thread>
#include <cstdint>
void race_test() {
// always works
//constexpr auto mem_order = std::memory_order_seq_cst;
// occasionally deadlocks
constexpr auto mem_order = std::memory_order_acq_rel;
std::atomic<std::size_t> data = 0;
std::atomic_bool flag = false;
constexpr std::size_t REP_COUNT = 2000;
std::thread dec_thread{[&] {
for (std::size_t i = 0; i != REP_COUNT; ++i) {
while (data.load() == 0) {
flag.wait(false);
flag.store(false, mem_order);
}
--data;
}
}};
std::thread inc_thread{[&] {
for (std::size_t i = 0; i != REP_COUNT; ++i) {
++data;
flag.store(true);
flag.notify_one();
}
}};
inc_thread.join();
dec_thread.join();
}
int main()
{
for (std::size_t i = 0; i != 1000; ++i)
race_test();
}
Ao mudar mem_order
para memory_order_seq_cst
tudo parece sempre funcionar corretamente.
Tudo o que posso imaginar como problema é que flag.store(false, mem_order);
é ordenado depois data.load()
(e o flag.store(true);
in inc_thread
), mas isso não deveria ser evitado por acq_rel
?
Pelo que entendi, memory_order_seq_cst
apenas evita a reordenação com outras memory_order_seq_cst
operações, mas então fazer tudo não deveria memory_order_acq_rel
corrigir isso? (não resolve)
Supondo que mudar flag.store(false)
para seq_cst
seja o suficiente para tornar isso correto, alguém poderia explicar o porquê? Eu apreciaria se alguém pudesse declarar a ordem mais "relaxada" para cada operação atômica que ainda mantém isso livre de raça.
Obrigado.