C++ 线程安全的singleton如何实现

线程安全的C++ 单例

饿汉模式

必须在main开始前就初始化好,于是,需要一个静态变量。可以是对象,也可以是对象的指针。如果是指针,在main开始前new一下就行。由于这个静态变量只会被单例类使用,所以最好不是静态全局变量,而是类的静态成员变量。

// 对象版
// singleton.h
class Singleton {
    private:
        Singleton() {}
        Singleton(const Singleton &);
        Singleton& operator= (const Singleton &);
        static Singleton s;
    public:
        static Singleton *GetInstance() {
            return &s;
        }
};
// singleton.cc
Singleton Singleton::s;

还有:

//指针版。没有析构逻辑,会造成泄露。
// singleton.h
class Singleton {
    private:
        Singleton() {}
        Singleton(const Singleton &);
        Singleton& operator= (const Singleton &);
        static Singleton *p;
    public:
        static Singleton *GetInstance() {
            return p;
        }
};
// singleton.cc
Singleton *Singleton::p = new Singleton;

优点:
线程安全
实现简单,容易维护

缺点:
不适合部分场景。如:因为性能问题,希望懒加载;需要运行时才能知道,是否生成实例
由于在main开始前就必须初始化,几乎不可能给类传入任何参数。

懒汉模式

1.返回引用

使用局部静态变量。局部静态变量的初始化是线程安全的,这一点由编译器保证(http://gcc.gnu.org/ml/gcc-patches/2004-09/msg00265.html,这是一个GCC的patch,专门解决这个问题)。会在程序退出的时候自动销毁。
见: http://stackoverflow.com/questions/270947/can-any-one-provide-me-a-sample-of-singleton-in-c/271104#271104
适合C++11,保证静态局部变量的初始化是线程安全的。如果是C++98就不能用这个方法。

class S
{
    public:
        static S& getInstance()
        {
            static S    instance;
            return instance;
        }
    private:
        S() {}
        S(S const&);              // Don't Implement.
        void operator=(S const&); // Don't implement
 };
加锁。线程安全,但每次都有开销。
// singleton.h
class Singleton {
    public:
        static Singleton *GetInstance() {
            lock();
            if (p == NULL) {
                p = new Singleton;
            }
            return p;
        }
    private:
    static Singleon *p;
    Singleton() {}
    Singleton(const Singleton &);
    Singleton& operator= (const Singleton &);
};

// singleton.cc
Singleton *Singleton::p = NULL;
pthread_once

陈硕推荐的做法

class Singleton {
    public:
        static Singleton *GetInstance() {
            pthread_once(&ponce_, &Singleton::init);
            return value_;
        }
        private:
        Singleton() {}
        Singleton(const Singleton &);
        Singleton& operator= (const Singleton &);
        static void init() {
            value_ = new T();
        }
        static pthread_once_t ponce_;
        static Singleton *value_;
};
pthread_once_t SIngleton::ponce_ = PTHREAD_ONCE_INIT;
Singleton* Singleton::value_ = NULL;
DCL

double check locking.只能用内存屏障,其他做法都是有问题的。
参见论文: http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
普通的double check之所以错,是因为乱序执行和多处理器下,不同CPU中间cache刷往内存并对其他CPU可见的顺序无法保障(cache coherency problem)。
Singleton<T> *p = new Singleton<T>;,那么实际有3步:
1. 分配内存
2. 构造
3. 赋值给p
2和3的顺序是未定的(乱序执行!)。因此,如果直接赋值给p那么很可能构造还没完成。此时另一个线程调用GetInstance,在lock外面check了一下,发现p!=NULL,于是直接返回p,使用了未初始化完成的实例,跪了。

那么,如果用中间变量转一下呢?用tmp_p转了下以后,tmp_p赋值给p的时候,显然p指向的实例是构造完成了的。然而,这个tmp_p在编译器看来明显没什么用,会被优化掉。

最佳实践?

1.eager initialize
2.次次加锁,但每个线程缓存了返回的指针,调用一次有用缓存的指针即可。
3.pthread_once

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值