Свободна ли следующая синглтонная реализация от гонки данных?
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);
}
Является ли std :: memory_model_acquire
операции загрузки лишнее? Можно ли еще больше ослабить операции загрузки и сохранения, переключив их на std :: memory_order_relaxed
? В этом случае семантика получения / выпуска std :: mutex
достаточна, чтобы гарантировать ее правильность, или еще один std :: atomic_thread_fence (std :: memory_order_release)
также требуется для гарантировать, что запись в память конструктора происходит до расслабленного хранилища? Тем не менее, эквивалентно ли использование забора наличию магазина с memory_order_release
?
EDIT : Благодаря ответу Джона, Я придумал следующую реализацию, в которой не должно быть гонки данных. Несмотря на то, что внутренняя нагрузка может быть вообще не атомарной, я решил оставить расслабленную нагрузку, чтобы она не влияла на производительность. По сравнению с постоянной внешней загрузкой с порядком получения памяти, механизм thread_local улучшает производительность доступа к экземпляру примерно на порядок.
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;
}