Являются ли следующие данные о гонке реализации однопользовательской версии?
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;
}