1
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 是否必须是 volatile 的?如果第一个线程创建 T 实例并假设第二个线程在此之前已经在缓存中加载了 0 的 obj,那么现代处理器缓存是否会失效,或者第二个线程可能会使用 obj 的 0 值并创建 T第二次?假设两个线程都在不同的内核中运行。

另外,请让我知道使用静态 T * 而不是静态 T 作为单例数据可能出现的任何问题。

4

5 回答 5

3

getInstance()方法是使用双重检查锁定。双重检查锁定本质上是危险的,因为可能会根据编译器为该行生成代码的方式引入竞争条件:

obj = new T;

这条线基本上涉及以下三个步骤:

  1. 分配sizeof (T)字节。
  2. T在分配的空间中构造一个对象。
  3. 将指向分配空间的指针分配给obj.

问题是,编译器不需要生成按顺序执行这些步骤的代码。例如,它可以分配sizeof (T)字节,然后分配obj然后T就地构造。在这种情况下,存在两个不同的线程可以构造一个新T对象的竞争条件。此外,两个不同的线程可以尝试T在同一个地方构造一个对象。

请参阅Scott Meyers 和 Andrei Alexandrescu的 C++ 和双重检查锁定的危险。

就 而言deleteInstance(),您可能不应该提供这样的功能,因为它允许您T在其他线程仍在使用它时意外地删除对象的错误。

编辑:避免双重检查锁定的实现getInstance()是:

static T * getInstance()
{
    lock.lock();
    if (!obj)
    {
        try
        {
            obj = new T;
        }
        catch (...)
        {
            obj = NULL;
            lock.unlock();
            throw;
        }
    }
    lock.unlock();
    return obj;
}
于 2012-08-25T22:04:55.567 回答
2

很好,getInstance没有必要做会员volatile

但是,如果从不同的线程调用它deleteInstance,它可能会执行双重删除(即使在检查后)。NULL

另一个问题是它deleteInstance不会重置objNULL,因此在删除后,getInstance将返回一个悬空指针,而不是创建一个新对象。

于 2012-08-25T22:01:36.477 回答
2

That is a horrible way of implementing a singleton (and implementing a singleton is a questionable practice in the first place...). If you are using a compiler which supports the relevant parts of C++11, you should use a static local variable:

template<typename T>
T& getInstance() {
    static T instance;
    return instance;
}

Otherwise you should use something like call_once from Boost.Thread:

//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;
}

Another option is to add a function to explicitly initialize your globals, and then to call that before starting any new threads.

于 2012-08-25T23:45:14.520 回答
1

您不需要使变量易失。互斥锁操作通常由 XCHG 处理器指令实现,该指令还用作内存屏障(至少在 Intel 处理器上)——它将强制从内存而不是缓存中读取变量。

于 2012-08-25T22:08:51.057 回答
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很危险,因为该类不知道是否有任何未完成的指针,因此调用此函数可能会将对象从某个无辜线程下拉出。

于 2012-08-26T00:30:18.777 回答