Então me deparei com as postagens maravilhosas do blog de Jeff Preshing sobre o que é Acquire
/ Release
e como elas podem ser alcançadas com algumas barreiras de CPU .
Também li que SeqCst
se trata de uma ordem total que é garantidamente consistente com a relação não coerente-depois - embora às vezes possa contradizer a relação acontece-antes estabelecida por operações simples Acquire
devido Release
a razões históricas.
Minha pergunta é : como os antigos built-ins do GCC mapeiam no modelo de memória proposto pelo C++11 (e revisões posteriores)? Em particular, como mapear __sync_synchronize()
no C++11 ou no C/C++ moderno posterior?
No manual do GCC, essa chamada é simplesmente descrita como uma barreira de memória completa , que eu suponho ser a combinação de todos os quatro principais tipos de barreira, ou seja, barreirasLoadLoad
/ LoadStore
/ StoreLoad
/ todas de uma vez. Mas é equivalente a ? Ou talvez , formalmente falando , uma delas seja mais forte que a outra (o que eu suponho ser o caso aqui: em geral, uma cerca deve ser mais forte, pois requer que a cadeia de ferramentas/plataforma improvise uma ordenação global de alguma forma, não?), e acontece que a maioria das CPUs por aí fornece apenas instruções que satisfazem ambas (barreira de memória completa por , ordenação sequencial total por ) de uma vez , por exemplo x86 e PowerPC ?StoreStore
sync_synchronize
std::atomic_thread_fence(memory_order_seq_cst)
SeqCst
__sync_synchronize
std::atomic_thread_fence(memory_order_seq_cst)
mfence
hwsync
Ou __sync_synchronize
e std::atomic_thread_fence(memory_order_seq_cst)
são formalmente iguais ou são efetivamente iguais (ou seja, formalmente falando, são diferentes, mas nenhuma CPU comercializada se preocupa em diferenciar entre os dois), tecnicamente falando, uma memory_order_relaxed
carga no mesmo atômico ainda não pode ser confiável para sincronizar com /criar acontece antes da relação com ele, certo?
Tecnicamente falando, todas essas afirmações podem falhar, certo?
// Experiment 1, using C11 `atomic_thread_fence`: assertion is allowed to fail, right?
// global
static atomic_bool lock = false;
static atomic_bool critical_section = false;
// thread 1
atomic_store_explicit(&critical_section, true, memory_order_relaxed);
atomic_thread_fence(memory_order_seq_cst);
atomic_store_explicit(&lock, true, memory_order_relaxed);
// thread 2
if (atomic_load_explicit(&lock, memory_order_relaxed)) {
// We should really `memory_order_acquire` the `lock`
// or `atomic_thread_fence(memory_order_acquire)` here,
// or this assertion may fail, no?
assert(atomic_load_explicit(&critical_section, memory_order_relaxed));
}
// Experiment 2, using `SeqCst` directly on the atomic store
// global
static atomic_bool lock = false;
static atomic_bool critical_section = false;
// thread 1
atomic_store_explicit(&critical_section, true, memory_order_relaxed);
atomic_store_explicit(&lock, true, memory_order_seq_cst);
// thread 2
if (atomic_load_explicit(&lock, memory_order_relaxed)) {
// Again we should really `memory_order_acquire` the `lock`
// or `atomic_thread_fence(memory_order_acquire)` here,
// or this assertion may fail, no?
assert(atomic_load_explicit(&critical_section, memory_order_relaxed));
}
// Experiment 3, using GCC built-in: assertion is allowed to fail, right?
// global
static atomic_bool lock = false;
static atomic_bool critical_section = false;
// thread 1
atomic_store_explicit(&critical_section, true, memory_order_relaxed);
__sync_synchronize();
atomic_store_explicit(&lock, true, memory_order_relaxed);
// thread 2
if (atomic_load_explicit(&lock, memory_order_relaxed)) {
// we should somehow put a `LoadLoad` memory barrier here,
// or the assert might fail, no?
assert(atomic_load_explicit(&critical_section, memory_order_relaxed));
}
Eu tentei esses snippets no meu RPi 5, mas não vejo falhas nas asserções. Sim, isso não prova nada formalmente, mas também não esclarece a diferenciação entre __sync_synchronize
e std::atomic_thread_fence(memory_order_seq_cst)
.