原始Double-Checked Locking模式的问题已有详细记录:C++ and the Perils of Double-Checked Locking。我已经经常看到关于SO的问题。
我想出了一个版本,对我而言,似乎可以解决原始模式的竞争条件问题,但它看起来对你好吗?
在下面的代码中,我假设LOCK是一个正确实现的互斥锁类型,它在锁定/解锁时会导致内存屏障,而且我不会尝试处理实例的释放,因为它不属于图案。
我知道其他可能的方法来做单身人士,但这不是我要求的。我特别询问模式 - 是否通过在互斥锁之外进行分配来解决竞争条件?
template <typename T, typename LOCK>
class Singleton
{
private:
static T * instance_;
static LOCK mutex;
// private - inaccessible
Singleton();
Singleton(const Singleton &);
Singleton & operator=(const Singleton &);
public:
static T * get_instance()
{
if (instance_ == NULL)
{
T * temp = new T;
mutex.lock();
if (instance_ == NULL)
instance = temp;
else
delete temp;
mutex.unlock();
}
return instance_;
}
};
答案 0 :(得分:3)
不,这不安全。 instance_
上存在竞争条件(一个线程可以从中读取(if (instance_ == NULL)
)而另一个线程正在写入它(instance = temp;
)),因此此代码具有未定义的行为。
您正在询问锁定创建的单个内存栅栏是否足够。从C ++ 11的角度来看,它不是。从非C ++ 11的角度来看,我不能肯定地说,但依赖于非原子和非互斥类型进行同步似乎不太可行(在C ++之前的世界中,互斥体和原子变量)只能通过编译器和处理器特定的黑客工作,依靠它们做任何比他们的裸规范更多的东西似乎是愚蠢的。)
答案 1 :(得分:1)
正如其他地方所提到的那样,问题是访问instance_
时存在数据竞争:第一个if语句读取值,后来赋值给instance_
写入该变量。由于这两个操作之间没有同步,因此行为未定义。但是如果你有C ++ 11,这是一个简单的解决方案。更改声明
static T * instance_;
到
static std::atomic<T *> instance_;
现在instance_
的所有访问都是原子的,这可以保证不会撕裂并提供同步。