template <typename T, typename Lock>
class Singleton
{
public:
static T * getInstance()
{
if (obj == 0)
{
lock.lock();
if (obj == 0)
{
obj = new T;
}
lock.unlock();
}
return obj;
}
void deleteInstance()
{
delete obj;
obj = 0;
}
private:
volatile static T * obj = 0;
static Lock lock; // some sort of mutex
Singleton();
~Singleton();
Singleton(const Singleton &);
Singleton & operator=(const Singleton &);
};
obj必须是不稳定的吗?如果是第一个线程,创建T实例并让缓存之前第二个线程已经加载obj为0,那么现代处理器缓存是否会失效,或者第二个线程可能使用obj的0值并创建T第二次?假设两个线程都在不同的核心中运行。
另外,请告诉我使用静态T *而不是静态T作为单例数据可能出现的任何问题。
答案 0 :(得分:3)
getInstance()
方法正在使用double-checked locking。双重检查锁定本质上是危险的,因为可能会引入竞争条件,具体取决于编译器如何为该行生成代码:
obj = new T;
这一行主要涉及以下三个步骤:
sizeof (T)
个字节。T
对象。obj
。问题是,编译器不需要生成按顺序执行这些步骤的代码。例如,它可以分配sizeof (T)
字节,然后就地分配obj
,然后构造T
。在这种情况下,存在竞争条件,其中两个不同的线程可以构造新的T
对象。此外,两个不同的线程可以尝试在同一个地方构建T
对象。
请参阅Scott Meyers和Andrei Alexandrescu的C++ and the Perils of Double-Checked Locking。
就deleteInstance()
而言,您可能不应该提供这样的功能,因为它允许您在其他线程仍在使用时意外地删除T
对象时出错。
编辑:getInstance()
的实现避免了双重检查锁定:
static T * getInstance()
{
lock.lock();
if (!obj)
{
try
{
obj = new T;
}
catch (...)
{
obj = NULL;
lock.unlock();
throw;
}
}
lock.unlock();
return obj;
}
答案 1 :(得分:2)
getInstance
没问题,也没有必要让会员volatile
。
deleteInstance
存在问题,如果从不同的线程调用它,可能会进行双重删除(即使在检查NULL
之后)。
另一个问题是deleteInstance
未将obj
重置为NULL
,因此在删除后,getInstance
将返回悬空指针,而不是创建新对象。
答案 2 :(得分:2)
这是实施单身人士的一种可怕方式(并且首先实施单身人士是一个值得怀疑的做法......)。如果您使用的是支持C ++ 11相关部分的编译器,则应使用静态局部变量:
template<typename T>
T& getInstance() {
static T instance;
return instance;
}
否则你应该使用Boost.Thread中的call_once:
//singleton.hpp
T& getInstance();
//singleton.cpp
static T* singleton;
static boost::once_flag flag=BOOST_ONCE_INIT;
static void initialize() {
singleton = new T();
}
T& getInstance() {
boost::call_once(flag, initialize);
return *singleton;
}
另一种选择是添加一个函数来显式初始化你的全局变量,然后在开始任何新线程之前调用它。
答案 3 :(得分:1)
您不需要将变量设为volatile。互斥锁定操作通常由XCHG处理器指令实现,该指令也充当内存屏障(至少在英特尔处理器上) - 它将强制从内存而不是从缓存中读取变量。
答案 4 :(得分:0)
如果你有C ++ 11,那么obj
为原子,getInstance
中的所有问题都会消失。
static std::atomic<T*> obj;
和
template <typename T, typename Lock>
std::atomic<T*> Singleton<T, Lock>::obj = 0;
deleteInstance
需要更多工作:
T *tmp = 0;
obj.exchange(tmp);
delete tmp;
但是deleteInstance
是危险的,因为类不知道是否有任何未完成的指针,所以调用这个函数可能会从一些无辜的线程中将对象拉出来。