对于c++实现的单例模式,在多线程环境中,存在的不可靠及其解决办法,由于能力限制,还没有完全摸索清楚,在此暂记。
因为在设计或开发中,肯定会有这么一种情况,一个类只能有一个对象被创建,如果有多个对象的话,可能会导致状态的混乱和不一致。这种情况下,单例模式是最恰当的解决办法。它有很多种实现方式,各自的特性不相同,使用的情形也不相同。今天要实现的是常用的三种,分别是饿汉式、懒汉式和多线程式。
通过单例模式,可以做到:
1、确保一个类只有一个实例被建立;
2、提供了一个对对象的全局访问指针;
3、在不影响单例类的客户端的情况下允许将来有多个实例。
懒汉式:就是说当你第一次使用时才创建一个唯一的实例对象,从而实现延迟加载的效果。因为此时可能存在多线程竞态环境,如不加锁限制会导致重复构造或构造不完全问题。
class CSingleton{
public:
static CSingleton *GetInstance()
{
if (m_pInstance == NULL)
{
m_pInstance = new CSingleton();
}
return m_pInstance;
}
private:
CSingleton(){};
static CSingleton *m_pInstance;
};
CSingleton *CSingleton::m_pInstance = nullptr;
GetInstance()
使用懒惰初始化,也就是说它的返回值是当这个函数首次被访问时被创建的。这是一种防弹设计——所有GetInstance()
之后的调用都返回相同实例的指针:
CSingleton *p1 = CSingleton::GetInstance();
CSingleton *p2 = p1->GetInstance();
CSingleton &ref = *CSingleton::GetInstance();
对GetInstance
稍加修改,这个设计模板便可以适用于可变多实例情况,如一个类允许最多五个实例。
代码很简单,但是会存在内存泄漏的问题,new出来的东西始终没有释放,下面是一种饿汉式的一种改进。
class CSingleton{
private:
CSingleton(){}
static CSingleton *m_pInstance;
class CGarbo{
public:
~CGarbo()
{
if(CSingleton::m_pInstance)
{
delete CSingleton::m_pInstance;
}
}
};
static CGarbo Garbo;
public:
static CSingleton * GetInstance()
{
if(m_pInstance == NULL)
{
m_pInstance = new CSingleton();
}
return m_pInstance;
}
};
CSingleton *CSingleton::m_pInstance = nullptr;
在程序运行结束时,系统会调用CSingleton的静态成员Garbo的析构函数,该析构函数会删除单例的唯一实例。使用这种方法释放单例对象有以下特征:
1、在单例类内部定义专有的嵌套类。
2、在单例类内定义私有的专门用于释放的静态成员。
3、利用程序在结束时析构全局变量的特性,选择最终的释放时机。
以上代码是非线程安全的,多线程环境需要加入同步机制:
class Singleton{
public:
//获取唯一实例的接口函数
static Singleton* GetInstance()
{
//双重检查,提高效率,避免高并发场景下每次获取实例对象都进行加锁
if (_sInstance == NULL)
{
std::lock_guard<std::mutex> lock(_mtx);
if (_sInstance == NULL)
{
Singleton* tmp = new Singleton;
MemoryBarrier(); //内存栅栏,防止编译器优化
_sInstance = tmp;
}
}
return _sInstance;
}
static void DelInstance()
{
if (_sInstance)
{
delete _sInstance;
_sInstance = NULL;
}
}
void Print()
{
std::cout << _data << std::endl;
}
private:
//构造函数定义为私有,限制只能在类内实例化对象
Singleton():_data(10){}
//防拷贝
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
private:
static std::mutex _mtx; //保证线程安全的互斥锁
static Singleton *_sInstance; //指向实例的指针定义为静态私有,这样定义静态成员获取对象实例
int _data; //单例类里面的数据
};
Singleton *Singleton::_sInstance = nullptr;
饿汉式的特点是一开始就加载了,如果说懒汉式是“时间换空间”,那么饿汉式就是“空间换时间”,因为一开始就创建了实例,所以每次用到的之后直接返回就好了。
class CSingleton{
private:
CSingleton(){};
public:
static CSingleton *GetInstance()
{
static CSingleton m_pInstance;
return &m_pInstance;
}
};
饿汉式是线程安全的,在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变。
在这篇文章中,指出了上面饿汉模式代码存在的线程安全问题,原因是:静态的局部变量是在调用的时候分配到静态存储区,所以在编译的时候没有分配。以下为线程安全的代码:
class CMsBsGPSInfoStart{
public:
static CMsBsGPSInfoStart &GetInstance();
protected:
CMsBsGPSInfoStart();
~CMsBsGPSInfoStart();
private:
static CMsBsGPSInfoStart _instance;
private:
//CLock m_lkMsBsGPSInfoStartFlag;
bool m_bMsBsGPSInfoStartFlag; //
public:
bool GetMsBsGPSInfoStart();
bool SetMsBsGPSInfoStart(bool bIsStart);
};
CMsBsGPSInfoStart CMsBsGPSInfoStart::_instance;
CMsBsGPSInfoStart::CMsBsGPSInfoStart():m_bMsBsGPSInfoStartFlag(false)
{
std::cout << "enter CMsBsGPSInfoStart::CMsBsGPSInfoStart()" << endl;
}
CMsBsGPSInfoStart::~CMsBsGPSInfoStart()
{
std::cout << "enter CMsBsGPSInfoStart::~CMsBsGPSInfoStart()" << endl;
}
CMsBsGPSInfoStart &CMsBsGPSInfoStart::GetInstance()
{
std::cout << "CMsBsGPSInfoStart::GetInstance()" << endl;
return _instance;
}
bool CMsBsGPSInfoStart::SetMsBsGPSInfoStart(bool bIsStart)
{
m_bMsBsGPSInfoStartFlag = bIsStart;
return true;
}
int main()
{
return 0;
}
在懒汉式的单例类中,其实有两个状态,单例未初始化和单例已经初始化。假设单例还未初始化,有两个线程同时调用GetInstance
方法,这时执行m_pInstance == NULL
肯定为真,然后两个线程都初始化一个单例,最后得到的指针并不是指向同一个地方,不满足单例类的定义了,所以懒汉式的写法会出现线程安全的问题!在多线程环境下,要对其进行修改。
这里要处理的是懒汉模式。
class CSingleton{
private:
static CSingleton* m_pInstance;
CSingleton(){}
public:
static CSingleton* GetInstance();
};
CSingleton* CSingleton::GetInstance()
{
if(NULL == m_pInstance)
{
Lock();//借用其它类来实现,如boost
if(NULL == m_pInstance)
{
m_pInstance= new CSingleton;
}
UnLock();
}
return m_pInstance;
}
CSingleton *CSingleton::m_pInstance = nullptr;
使用double-check来保证线程安全。但是如果处理大量数据时,该锁才成为严重的性能瓶颈。
注:其实以上的加锁方式是不可靠的。参考:
https://blog.csdn.net/nodeathphoenix/article/details/51657973
https://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
https://silviuardelean.ro/2012/06/05/few-singleton-approaches/