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

来源:互联网 发布:facebook广告优化技巧 编辑:程序博客网 时间:2024/04/24 19:29

线程安全的C++ 单例

饿汉模式

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

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

还有:

//指针版。没有析构逻辑,会造成泄露。// singleton.hclass Singleton {    private:        Singleton() {}        Singleton(const Singleton &);        Singleton& operator= (const Singleton &);        static Singleton *p;    public:        static Singleton *GetInstance() {            return p;        }};// singleton.ccSingleton *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.hclass 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.ccSingleton *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 0
原创粉丝点击