Свободна ли следующая гонка данных для одной реализации?
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
?
РЕДАКТИРОВАТЬ : Благодаря ответу Джона я придумал следующую реализацию, которая должна бытьРаса свободна.Хотя внутренняя нагрузка может быть вообще не атомарной, я решил оставить расслабленную нагрузку, поскольку она не влияет на производительность.По сравнению с тем, чтобы всегда иметь внешнюю нагрузку с порядком получения памяти, механизм 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;
}