• 认真地记录技术中遇到的坑!
  • 能摸鱼真是太好啦!嘿嘿嘿!

单例模式(C++线程安全版)

单例模式 Moxun 6年前 (2018-03-26) 5873次浏览 0个评论

单例模式

单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点。

懒汉模式

即在第一次调用该类实例的时候才产生一个新的该类的实例,并在以后仅返回该类实例。

  1. double-check静态成员实例的懒汉模式
#ifndef SINGLETON_H_
#define SINGLETON_H_

#include 
#include 
#include 
#include 

class Singleton
{
public:
    static Singleton * getInstance();

    ~Singleton() = default;
    void doSomething()
    {
        std::cout << "I am a Coder!" << std::endl;
    }

private:
    static std::mutex m_Mutex;
    static Singleton *m_Instance;
    Singleton() = default;
};
#endif

#define SINGLETON Singleton::getInstance()

Singleton * Singleton::m_Instance = nullptr;

Singleton * Singleton::getInstance()
{
    if (nullptr == m_Instance)
    {
        std::lock_guard lk(m_Mutex);
        if (nullptr == m_Instance)
        {
            m_Instance = new Singleton;
        }
    }
}

这是一个基于double-check模式实现的单例类,它的缺点是有大量数据需要处理时,锁会成为性能瓶颈(注意,这里的性能不要以正常的思维考虑性能瓶颈的问题,因为CPU的运算速率非常非常快),它另外一个致命缺点是非线程安全。

非线程安全的原因是,会有多个线程进入if判断,而new运算符实际上不是原子操作,它被分为三个步骤,分配内存,在分配好的内存上构造对象,令指针指向这一内存区域然后因为硬件优化的原因,在实际执行new操作的时候,它的顺序可能并不是我们设想的这样,第二步和第三步可能会被颠倒,那么此时假设有A、B两个线程进入了上述getInstance()的代码段,B线程先进来,此时指针为空,那么它去执行锁内的操作,假设此时new操作的顺序是先分配内存,然后令指针指向分配好的内存然后在该内存区域上创建对象,那么在执行到第二步的时候由于CPU线程调度,B线程被挂起了,于此同时A线程此时看到的m_Instance指针不为空,可是它并没有指向实际对象,但是A线程不知道,由此产生了数据竞争的问题。

C++11为标准库提供了std::once_flag和std::call_once来处理这种情况,每个线程都使用std::call_once,到std::call_once返回时,指针将会被某个线程初始化(完全同步的形式)。基于此,我们对上述代码改造可以得到一个线程安全的单例,示例代码如下:

#ifndef SINGLETONC11_H_
#define SINGLETONC11_H_

#include 
#include 
#include 
#include 

class SingletonC11
{
public:


    //注意调用顺序
    SingletonC11 * getInstance()
    {
        std::call_once(initOnce, &SingletonC11::createInstance, this);
        createInstance();
        return m_Instance;
    }

    ~SingletonC11() = default;
    void doSomething()
    {
        std::cout << "I am a Coder!" << std::endl;
    }

private:

    static std::once_flag initOnce;
    static std::mutex m_Mutex;
    static SingletonC11 *m_Instance;
    static void createInstance();

    SingletonC11() = default;
};
#endif

#define SINGLETONC11 SingletonC11::acquire()

SingletonC11 * SingletonC11::m_Instance = nullptr;

void SingletonC11::createInstance()
{
    m_Instance = new SingletonC11;
}

注:这个例子写的并不好,请斧正

  1. 内部静态实例的懒汉模式
class SingletonInside
 {
  private:
      SingletonInside(){}
  public:
      static SingletonInside* getInstance()
      {
          Lock(); // not needed after C++0x
          static SingletonInside instance;
         UnLock(); // not needed after C++0x
         return instance; 
     }
 };

在C++11中保证了局部static变量是线程安全的,它在时间控制首次经过其声明时发生,初始化被定义为只能发生在一个线程上并且其它线程不可以继续直到初始化完成。

  1. 饿汉模式
    即无论是否使用该类的实例,在程序开始时都创建一个该类的实例,并在以后只返回这个实例,示例代码:
 class SingletonStatic
  {
  private:
      static const SingletonStatic* m_instance;
      SingletonStatic(){}
  public:
      static SingletonStatic* getInstance()
      {
          return m_instance;
     }
 };

 //外部初始化 before invoke main
 const SingletonStatic* SingletonStatic::m_instance = new SingletonStatic;

全局static变量的初始化发生在main函数开始之前,所以这种形式下的单例是线程安全的。

但是上述单例模式依然存在问题,假设我们有一些操作,例如关闭文件,需要在析构函数中完成。程序在结束的时候,系统会自动析构所有的全局变量。事实上,系统也会析构所有的类的静态成员变量,就像这些静态成员也是全局变量一样。利用这个特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例。这个垃圾回收类具有如下特点:

类CGarbo被定义为CSingleton的私有内嵌类,以防该类被在其他地方滥用。

程序运行结束时,系统会调用CSingleton的静态成员Garbo的析构函数,该析构函数会删除单例的唯一实例。

使用这种方法释放单例对象有以下特征:

在单例类内部定义专有的嵌套类;

在单例类内定义私有的专门用于释放的静态成员;

利用程序在结束时析构全局变量的特性,选择最终的释放时机;

使用单例的代码不需要任何操作,不必关心对象的释放。

示例代码:

#include  

using namespace std;

class Singleton
{
public:
    static Singleton *GetInstance();
private:
    Singleton()
    {
        cout << "Singleton ctor" << endl;
    }
    ~Singleton()
    {
        cout << "Singleton dtor" << endl;
    }
    static Singleton *m_pInstance;
    class Garbo
    {
        public:
        ~Garbo()
        {
            if (Singleton::m_pInstance)
            {
                cout << "Garbo dtor" << endl;
                delete Singleton::m_pInstance;
            }
        }
    };
    static Garbo garbo;

};

Singleton::Garbo Singleton::garbo;  // 一定要初始化,不然程序结束时不会析构garbo  

Singleton *Singleton::m_pInstance = NULL;

Singleton *Singleton::GetInstance()
{

    if (m_pInstance == NULL)
        m_pInstance = new Singleton;

    return m_pInstance;
}

int main()
{
    Singleton *p1 = Singleton::GetInstance();
    Singleton *p2 = Singleton::GetInstance();
    if (p1 == p2)
        cout << "p1 == p2" << endl;
    return 0;
}  

参考:
https://blog.csdn.net/fu_zk/article/details/11892095


喜欢 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址