以下单例实现是否没有数据竞争?
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);
}
Is the 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;
}
我认为这是一个很好的问题,约翰·卡尔斯贝克有正确的答案。
然而,需要明确的是,惰性单例最好使用经典的 Meyers 单例来实现。它保证了 C++11 中的正确语义。
§ 6.7.4
...如果控制进入
变量初始化时同时声明,并发执行需等待
完成初始化。 ...
Meyer 的单例是首选,因为编译器可以积极优化并发代码。如果编译器必须保留 a 的语义,它会受到更多限制std::mutex
。此外,迈耶的单例是2 lines而且几乎不可能出错。
这是迈耶单例的经典示例。简单,优雅,又在c++03中破碎。但 c++11 简单、优雅、功能强大。
class Foo
{
public:
static Foo& instance( void )
{
static Foo s_instance;
return s_instance;
}
};
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)