Bloqueio Singleton com verificação dupla em C ++ 11
A seguinte implementação de singleton é livre de corrida?
static std::atomic<Tp *> m_instance;
...
static Tp &
instance()
{
if (!m_instance.load(std::memory_order_relaxed))
{
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_instance.load(std::memory_order_acquire))
{
Tp * i = new Tp;
m_instance.store(i, std::memory_order_release);
}
}
return * m_instance.load(std::memory_order_relaxed);
}
É ostd::memory_model_acquire
da operação de carregamento supérfluo? É possível relaxar ainda mais as operações de carregamento e armazenamento, alternando-as parastd::memory_order_relaxed
? Nesse caso, é a semântica de aquisição / liberação destd::mutex
o suficiente para garantir sua correção, ou maisstd::atomic_thread_fence(std::memory_order_release)
também é necessário para garantir que as gravações na memória do construtor ocorram antes da loja relaxada? No entanto, o uso de cerca é equivalente a ter a loja commemory_order_release
?
EDITA: Graças à resposta de John, eu vim com a seguinte implementação que deve ser livre de corrida de dados. Mesmo que a carga interna possa ser atômica, decidi deixar uma carga relaxada, pois ela não afeta o desempenho. Em comparação a sempre ter uma carga externa com a ordem de aquisição de memória, o mecanismo thread_local melhora o desempenho de acessar a instância de aproximadamente uma ordem de magnitud
static Tp &
instance()
{
static thread_local Tp *instance;
if (!instance &&
!(instance = m_instance.load(std::memory_order_acquire)))
{
std::lock_guard<std::mutex> lock(m_mutex);
if (!(instance = m_instance.load(std::memory_order_relaxed)))
{
instance = new Tp;
m_instance.store(instance, std::memory_order_release);
}
}
return *instance;
}