以下单例实现线程安全吗? :: Instance方法应该是正确的,Dispose是我自己的创建,所以要确保我没有忽略任何东西。
std::atomic<S *> S::_instance;
std::mutex S::_singleton_mutex;
S& S::Instance()
{
using namespace std;
S * tmp = _instance.load(memory_order_relaxed);
atomic_thread_fence(memory_order_acquire);
if (tmp == nullptr)
{
lock_guard<mutex> l(_singleton_mutex);
tmp = _instance.load(memory_order_relaxed);
if (tmp == nullptr)
{
tmp = new S();
atomic_thread_fence(memory_order_release);
_instance.store(tmp, memory_order_relaxed);
}
return *tmp;
}
void S::Dispose()
{
using namespace std;
S * tmp = _instance.load(memory_order_relaxed);
atomic_thread_fence(memory_order_acquire);
if (tmp != nullptr)
{
lock_guard<mutex> l(_singleton_mutex);
tmp = _instance.load(memory_order_relaxed);
if (tmp != nullptr)
{
atomic_thread_fence(memory_order_release);
_instance.store(nullptr, memory_order_relaxed);
delete tmp;
}
}
}
答案 0 :(得分:2)
解决方案是:是的,看起来不错。
更多信息:
如果您可能在短时间内有两个实例,第二个实例将被立即销毁,您可以摆脱互斥锁:
std::atomic<S *> S::_instance;
S& S::Instance()
{
using namespace std;
auto tmp = _instance.load(memory_order_relaxed);
if (tmp == nullptr)
{
auto tmp2 = new S();
if( !_instance.compare_exchange_strong(tmp, tmp2) )
delete tmp2;
}
return *tmp;
}
void S::Dispose()
{
using namespace std;
auto tmp = _instance.load(memory_order_relaxed);
if (tmp != nullptr)
{
if( _instance.compare_exchange_strong(tmp, nullptr) )
delete tmp;
}
}
当两个线程同时启动Instance()时,两者都会看到nullptr
并创建一个新的S
。只有其中一个将成功替换实例指针,而另一个将立即删除新的S.
无论如何,您可能更喜欢使用Scott Meyers单身,但它没有提供处理对象的方法:
S& S::Instance()
{
// This instance will be created at first call - thread safe.
// it lives until the program terminates.
static Singleton instance;
return instance;
}
这是最优雅,最小的代码和线程安全,顺便说一句。
答案 1 :(得分:1)
如其他答案所述,实施似乎很好。但是,存在一个概念性问题:用户和实例处理者之间的竞争条件。
Thread A: var i = s::Instance();
Thread B: s::Dispose();
Thread A: i.doSth();
可能存在一些用例,你可以保证这种情况永远不会发生;除此以外, 引用计数可能是解决此问题的方法。