zoukankan      html  css  js  c++  java
  • Design pattern : Singleton

    Singleton
    语义: 在整个应用程序里只能创建唯一一个对象,并且提供全局的访问点:
    实现:
    1.先看一个经常性的一个错误实现:    
           
    class Singleton
    {
    private:
    Singleton() {};

    如果没有定义 copy constructor 和 assignment operator ,编译器会实现,就可以根据已有的对象构造新对象,就不能保证唯一×/
    Singleton(
    const Singleton & ); // no implement
    Singleton & operator = ( const Singleton & ); // no implement

    public:
    static Singleton & getInstance()
    {
    if (NULL == m_pinstance)
    {
    m_pinstance
    = new Singleton();
    }

    return *m_pinstance;
    }
    private:
    static Singleton *m_pinstance;
    };

    int main()
    {
    Singleton
    & singleton = Singleton::getInstance();
    }
     Singleton 模式,其构造函数是私有的。 防止编译器产生拷贝构造和复制构造,也显示声明。
     提供静态的接口 static getInstance(),和静态成员变量 m_pinstance。
     
     那错在哪里?
     连接通不过。找不到 m_pinstance 定义。
     原来是:类中静态变量只是声明,需要放在外面实现文件(通常是.cpp) 里面定义。
     改正一下:
    View Code
    // Singleton.h
    class Singleton
    {
    private:
    Singleton();

    Singleton(
    const Singleton & ); // no implement
    Singleton & operator = ( const Singleton & ); // no implement

    public:
    static Singleton & getInstance()
    {
    if (NULL == m_pinstance)
    {
    m_pinstance
    = new Singleton();
    }

    return *m_pinstance;
    }


    private:
    static Singleton *m_pinstance;
    };

    // Singleton.cpp
    #include" Singleton.h"
    Singleton
    * Singleton::m_pinstance;
    Singleton::Singleton()
    {}


    //test.h
    #include" Singleton.h"
    int main()
    {
    Singleton
    & singleton = Singleton::getInstance();
    }

     问题: 申请的内存什么时候释放?

        因为Singleton 对象是在Heap上面申请的, 那么应该由delete 对象来释放内存,调用析构函数。
        但是因为这个对象比较特殊,其生命周期应该在应用程序结束的时候才能delete 掉,但是应该在哪里写delete 函数呢?复杂的应用程序很难。最好系统可以自己释放掉。
        那就想想起来 static 对象的特点,当应用程序退出来的时候才调用其析构函数。
        所以有下面的代码:
     
    class Singleton
    {
    private:
    Singleton(){}

    Singleton(
    const Singleton & ); // no implement
    Singleton & operator = ( const Singleton & ); // no implement
    ~Singleton() { //release resource }

    public:
    static Singleton & getInstance()
    {
       
    static Singleton singleton;

       
    return singleton;
    }
    };

    Singleton::Singleton()
    {
    cout
    <<"Singleton::Singleton()"<<endl;
    }

    int main()
    {
    Singleton
    & singleton = Singleton::getInstance();
    }
    由 getInstance方法返回一个静态对象,再应用程序推出main 函数后调用Singleton::~Singleton(). [注意Singleton::~Singleton() 此刻是私有的,也没有问题]
    另外还有一个变种,利用静态的智能指针来控制堆上的对象的析构:原理和上面的一样。
    View Code
    #include <memory>
    #include
    <iostream>
    using namespace std;

    class Singleton
    {
    public:
    static Singleton * Instance()

    {

    if( 0== _instance.get())

    {

    _instance.reset(
    new Singleton);

    }

    return _instance.get();

    }

    protected:

    Singleton(
    void)

    {

    cout
    <<"Create Singleton"<<endl;

    }

    virtual ~Singleton(void)

    {

    cout
    << "Destroy Singleton"<<endl;

    }

    friend
    class auto_ptr<Singleton>;

    static auto_ptr<Singleton> _instance;

    };

    //Singleton.cpp

    auto_ptr
    <Singleton> Singleton::_instance;

    比较两个优缺点:
    基于static 成员变量的,其静态成员存在于代码的静态存储区,如果其对象太大,就会增加程序loading 的时间,但是对于现在的计算机,这点开销可以忽略 不计。
    基于static 智能指针的,其使用了非OO的friend 技术,不能扩展到其他纯OO的语言里面。 但是其可以写成模板类,被singleton 的模型继承,如 http://www.cppblog.com/dyj057/archive/2005/09/20/346.aspx .

    Note:
       这里面用到好多static 变量的一些用法,总结一下:
             1. 类的static 变量定义在类的外面(cpp) 文件里,而其声明是在类的声明里面(.h)
             2. Laze initialzation
             3. Static 变量的存储空间 在 静态区,增加程序的加载时间。
             4. 静态变量的初始化顺序。 对于一个编译单元(文件) 可以保证初始化顺序,但是对于在与不同编译单元的变量,就是没有定义初始化顺序。
                 当静态变量有依赖关系且在不同的编译单元,解决办法是:对于没有全局静态变量放到一个全局函数里面,调用函数可以认为定义静态变量的初始化顺序。
             5.静态对象的析构, 这是由程序运行退出 main 函数之后调用的

    问题: 线程安全吗?

       当有两个以上的线程同时调用 get_Instance 会创建多个Singleton 对象。如何解决?
       
    下面来自于http://www.cnblogs.com/rocketfan/archive/2009/12/05/1617759.html 的转载:

    线程安全的C++ singleton模式

    • 最简单的singleton
    注:下面有些来自原文,有些来自"非完美Singleton实现",其它参考之3。
    代码
     1  // from the header file
     2  class Singleton {
     3  public:
     4  static Singleton* instance();
     5  ...
     6  private:
     7  static Singleton* pInstance;
     8  };
     9 
    10  // from the implementation file
    11  Singleton* Singleton::pInstance = 0;
    12 
    13  Singleton* Singleton::instance() {
    14  if (pInstance == 0) {
    15     pInstance = new Singleton;
    16  }
    17  return pInstance;
    18  }
    如 果在单线程模式那么上面的代码除了instance()可能会有异常安全问题外没有太大问题。但是对于多线程而言,如果两个线程在14判断都读到 pInstance = 0同时进入15,那么就会产生两个Singleton对象。而pInstance指向后产生的那一个。

     【如果将指针换为静态对象也可以,就避免上面所讨论的内存泄露】

    •  加锁保护的singleton
    代码
    1 Singleton* Singleton::instance() {
    2     Lock lock// acquire lock (params omitted for simplicity)
    3     if (pInstance == 0) {
    4         pInstance = new Singleton;
    5     }
    6     return pInstance;
    7 // release lock (via Lock destructor)

    这样是绝对的安全了,但是由于加锁解锁的代价大,而instance又是可能被频繁调用的函数所以大大影响性能。事实上只要pInstance == 0的时候才可能出现问题,需要加锁,那么有了下面的写法:代码

    1 Singleton* Singleton::instance() {
    2     if (pInstance == 0) {
    3         Lock lock// acquire lock (params omitted for simplicity)
    4         pInstance = new Singleton;
    5     }
    6     return pInstance;
    7 // release lock (via Lock destructor)

     但是这样是错误的,因为两个线程如果同时在2判断为true,虽然会在3处互斥,但是还是会轮流进入保护区,生成两个Singleton.于是有人想到下面的方法。

    • DCL方法(Double Checked  Locking)
    这也是ACE中Singleton的实现方法:

     代码

    1 Singleton* Singleton::instance() {
    2     if (pInstance == 0) { // 1st test
    3         Lock lock;
    4         if (pInstance == 0) { // 2nd test
    5             pInstance = new Singleton;
    6         }
    7     }
    8     return pInstance;
    9 }

    这看上去很完美但是它也是有问题的

    在编译器未优化的情况下顺序如下:
    1.new operator分配适当的内存;
    2.在分配的内存上构造Singleton对象;
    3.内存地址赋值给_instance。


    但是当编译器优化后执行顺序可能如下:
    1.new operator分配适当的内存;
    2.内存地址赋值给_instance;
    3.在分配的内存上构造Singleton对象。

     编译器优化后的代码看起来像下面这样:

     代码

     1 Singleton* Singleton::instance() {
     2     if (pInstance == 0) {
     3         Lock lock;
     4         if (pInstance == 0) {
     5             pInstance = // Step 3
     6               operator new(sizeof(Singleton)); // Step 1
     7             new (pInstance) Singleton; // Step 2
     8          }
     9     }
    10     return pInstance;
    11 }

     这样如果一个线程按照编译器优化的顺序执行到5,这时后pInstance就已经非0了,而实际上它所指向的内存上Singleton对象还没有 被构造,这个时候有可能另一个线程运行到2,发现pInstance不是0,NULL,于是return pInstance,假设它又用这个指向还未构造对象的指针调用pInstance->doSomeThing() .........:(

     未了避免这种情况,于是有了下面的做法

     代码

     1 Singleton* Singleton::instance() {
     2      if (pInstance == 0) {
     3          Lock lock;
     4          if (pInstance == 0) {
     5               Singleton* temp = new Singleton; // initialize to temp
     6               pInstance = temp; // assign temp to pInstance
     7          }
     8      }
     9      return pInstance;
    10 }

    企图利用一个临时变量,仅当Singleton对象构造完成后才把地址赋给pInstance,然而很不幸,编译器会把你的临时变量视为无用的东西从而优化掉。。。。 

    这里插一句为什么用pthread之类的库能够解决问题,保证顺序,因为它们是非语言本身的,往往 强迫编译器产生与之适应的代码,往往会调用系统调用,很多是用汇编实现的。

    • 加入volatile
    前面讲到volatile的定义,那么这里能否利用 volatile呢。上面最后的代码想法不错,只是编译器会捣乱:)那么我们加入volatile来避免编译器的优化,保证语句的顺序执行。不过这里我们 考虑这种情况,Singlton带有一个变量x,默认的私有构造函数会将其赋值为5.
    代码
     1 class Singleton {
     2  public:
     3      static Singleton* instance();
     4      ...
     5  private:
     6      static Singleton* volatile pInstance; // volatile added
     7      int x;
     8      Singleton() : x(5) {}
     9  };
    10  
    11  // from the implementation file
    12  Singleton* Singleton::pInstance = 0;
    13  
    14  Singleton* Singleton::instance() {
    15      if (pInstance == 0) {
    16          Lock lock;
    17          if (pInstance == 0) {
    18              Singleton* volatile temp = new Singleton; // volatile added
    19              pInstance = temp;
    20          }
    21      }
    22      return pInstance;
    23 }

    下面考虑编译器inline构造函数和之后的instance()函数的样子

     代码

    1 if (pInstance == 0) {
    2     Lock lock;
    3     if (pInstance == 0) {
    4         Singleton* volatile temp =
    5         static_cast<Singleton*>(operator new(sizeof(Singleton)));
    6         temp->= 5// inlined Singleton constructor
    7         pInstance = temp;
    8     }
    9 }

    问题出来了,尽管temp被声明为volatile,但是*temp不是,这意味着temp->x也不是,这意味着编译器可能会把6,7句换位置,从而使得另一个线程得到一个指向x域并未被构造好的Sigleton对象!

    解决办法是我们不仅仅声明pInstance为volatile也将*pInstance声明为volatile.如下:

    代码
     1 class Singleton {
     2 public:
     3     static volatile Singleton* volatile instance();
     4     ...
     5 private:
     6     // one more volatile added
     7     static Singleton* volatile pInstance;
     8 };
     9 
    10 // from the implementation file
    11 volatile Singleton* volatile Singleton::pInstance = 0;
    12 volatile Singleton* volatile Singleton::instance() {
    13     if (pInstance == 0) {
    14         Lock lock;
    15         if (pInstance == 0) {
    16             // one more volatile added
    17             volatile Singleton* volatile temp = new volatile Singleton;
    18             pInstance = temp;
    19         }
    20     return pInstance;
    21 }

    这里Lock不需要声明为volatile因为它来自其它的线程库入pthread会提供相应保证其前面的代码不会被编译器调到后面,它后面的代码也不会被调到前面,相当于栅栏效应。

         也许到现在这个版本是很完美的了,但是仍然可能失败,有下面两个原因:

    1. the Standard’s constraints on observable behavior are only for an abstract machine defined by the Standard, and that abstract machine has no notion of multiple threads of execution. As a result, though the Standard prevents compilers from reordering reads and writes to volatile data within a thread, it imposes no constraints at all on such reorderings across threads. 也就是说还是上面volatile解释语义时提到的,volatile,只保证线程内部顺序,不保证线程之间的
    2. 就像const变量直到其构造函数完成之后不是const的一样,volatile变量直到其构造函数和完成之前也不是volatile的volatile Singleton* volatile temp = new volatile Singleton; 要生成的变量直到new volatile Singleton完成之后才是volatile的。这意味这temp->x在构造函数中不是volatile的从而还是可能产生上面的问题和pInstance = temp 被编译器调换顺序。这个问题是可以解决的,办法如下,对默认构造函数改写,将对x的初始化列表构造初始化改为在构造函数中显示赋值。
    1 Singleton()
    2 {
    3     static_cast<volatile int&>(x) = 5// note cast to volatile
    4 }    
    这样的work around就解决了问题。

    编译器会生成类似下面的代码:

     代码

     1 Singleton* Singleton::instance()
     2 {
     3     if (pInstance == 0) {
     4         Lock lock;
     5         if (pInstance == 0) {
     6            Singleton* volatile temp =
     7              static_cast<Singleton*>(operator new(sizeof(Singleton)));
     8            static_cast<volatile int&>(temp->x) = 5;
     9            pInstance = temp;
    10         }
    11     }
    12 }

    这保证了8和9不会被调换顺序。

    • 一个可行的解决方案

     如果你的程序运行在多处理器机器上,会面临CACHE一致性问题,就是说每一个处理器会有自己的CACHE, 而所有的处理器有共享的MAIN CACHE,什么时候将自己的CAHE内容写到MAIN CACHE中,以及什么时候去从MAIN CACHE中读数据都是问题,编译器有时候可能会把多个写入MAIN CACHE的数据按照地址升序写入已达到最佳速度,从而调换代码执行顺序。一般而言解决CACHE一致性问题采用栅栏,barrier方法。

    代码
     1 Singleton* Singleton::instance () {
     2     Singleton* tmp = pInstance;
     3     
     4     ... // insert memory barrier
     5     
     6     if (tmp == 0) {
     7         Lock lock;
     8         tmp = pInstance;
     9         if (tmp == 0) {
    10             tmp = new Singleton;
    11     
    12     ... // insert memory barrier
    13             pInstance = tmp;
    14         }
    15     }
    16     return tmp;
    17 }

    事实上我们不需要完整的双向栅栏,上面一处栅栏只需要保证前面的代码不要提到栅栏后面去,下面的栅栏保证后面的代码不要挪到栅栏前面去。 

    但是注意栅栏是平台相关的,尤其是assembler. 如果你的平台支持栅栏那么这就是一个可行的解决方案。注意GCC4.4.2中的string对于ref count引用计数,就是采用原子操作加栅栏实现的。

    •  线程安全的Singleton模式总结
    1.首先这里上面主要是讨论线程安全的问题,其实可以有很多可改变的地方。比如说第一种加锁的方案它是简洁安全的但是效率低,我们其实可以建议客户端代码这样写来减少对instance函数的调用,只需要先保存一个Singleton指针即可。

    Singleton *instance = Singleton::instance();

    instance->doThing1();

    instance->donThing2();

    而不要每次都调用instance(), Singleton::instance()->doThings().

     也可以先做实验看看直接用这种加锁的方案是否真的很大程度影响了效率值得你去改变它。 

    2. 另外我们上面演示的代码 都是采用了lazily-initialized Singleton,就是说只有用到的时候才会有Singleton对象产生,如果程序没有调用instance就不会有Singleton对象的产生。 这样固然比eager initialization好,但是真的那么有必要吗,如果我们采用 eager initialization在进入main之前产生Singleton对象

    而一般的多线程程序都是在进入main后再启动的其它线程,就是说进入 mian之前是单线程环境,于是就没有这么多问题了。。。。boost 就是这么干的。

    "1) sington 在进入main函数前初始化.

    2)第一次使用时, singlton已得到正确的初始化(包括在static code中情况). Written by gavinkwoe"

    "由于create_object将在被调用(static object_type & instance())之前进行初始化,因此singleton_default对象的初始化被放到了main之前。非常巧妙的一个设计" 

     可以参考 游戏人生博客 Writen by Fox(yulefox.at.gmail.com) 设计模式(三)——Singleton

    http://www.cppblog.com/Fox/archive/2009/09/22/96898.html  http://www.yulefox.com/20081119/design-patterns-03.html/

    代码
     1 template <typename T>
     2 struct singleton
     3 {
     4   private:
     5         struct object_creator
     6        {
     7                object_creator() { singleton<T>::instance(); }  // 创建实例
     8                inline void do_nothing() const { } 
     9        };
    10      static object_creator create_object;
    11      singleton();
    12   public:
    13      typedef  T  object_type;
    14      static object_type & instance()
    15     {
    16          static object_type obj;
    17          create_object.do_nothing();// 需要create_object的初始化
    18          return obj;
    19     }
    20 };
    21 template <typename T>  typename singleton<T>::object_creator singleton<T>::create_object; 
    22  

     这个设计用到了模板可复用,如Singleton<MySingletonClass>::instance().

     3. 如果我们允许每个线程能有一个自己的Singleton对象呢,那样就既可以推迟Singleton对象生成,也不用考虑多线程问题。

    怎么解决?
  • 相关阅读:
    floating IP 原理分析
    创建 floating IP
    Why Namespace?
    虚拟 ​router 原理分析- 每天5分钟玩转 OpenStack(101)
    链接脚本使用一例2---将二进制文件 如图片、MP3音乐、词典一类的东西作为目标文件中的一个段
    linux-2.6.26内核中ARM中断实现详解(转)
    有关Cache –(1) linux list之中的Prefetc
    Linux 内核中的 GCC 特性
    对entry-common.S和call.S的部分理解1
    kernel&uboot学习笔记
  • 原文地址:https://www.cnblogs.com/zhyg6516/p/1982644.html
Copyright © 2011-2022 走看看