Double-Checked Lock Singleton en C ++ 11
Es gratuita la siguiente carrera de datos de implementación de singleton?
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);
}
Es elstd::memory_model_acquire
de la operación de carga superflua? ¿Es posible relajar aún más las operaciones de carga y almacenamiento al cambiarlas astd::memory_order_relaxed
? En ese caso, es la adquisición / liberación semántica destd::mutex
suficiente para garantizar su corrección, o una @ adicionstd::atomic_thread_fence(std::memory_order_release)
@ también se requiere para asegurar que las escrituras en la memoria del constructor sucedan antes de la tienda relajada? Sin embargo, ¿es equivalente el uso de una cerca para tener la tienda conmemory_order_release
?
EDITA: Gracias a la respuesta de John, se me ocurrió la siguiente implementación que debería estar libre de datos. Aunque la carga interna podría no ser atómica, decidí dejar una carga relajada ya que no afecta el rendimiento. En comparación para tener siempre una carga externa con el orden de adquisición de memoria, la maquinaria thread_local mejora el rendimiento de acceso a la instancia de aproximadamente un orden 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;
}