在程序设计中,难免有一种类会反复被实例化调用。如果这种类被反复实例化,会浪费很多内存空间和实例化过程中的性能下降。然而,class="superseo">单例模式很好的解决了这个问题。
下面来看一下懒汉单例模式实现
单例模式(线程不安全)class Singleton
{
public:
static Singleton* get_instance()
{
if(!m_instance) m_instance = new Singleton();
return m_instance;
}
private:
static Singleton* m_instance;
Singleton(){}
~Singleton(){}
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
};
Singleton* Singleton::m_instance;
上面的写法在单线程下是没有问题的,但是多线程情况下,无法保证只生成一个实力,因为我们知道get_instance中line6处存在对临界资源的访问。有关线程安全的讨论在下面会讲到。
单例模式(线程安全) 1. 加锁刚刚我们说道,get_instance中line6处存在对临界资源的访问。对于临界资源的反问,需要加锁进行处理
class Singleton
{
public:
static Singleton* get_instance()
{
if(!m_instance)
{
lock_guard<mutex> lg(m_mutex);
m_instance = new Singleton();
}
return m_instance;
}
private:
static Singleton* m_instance;
static mutex m_mutex;
Singleton(){}
~Singleton(){}
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
};
Singleton* Singleton::m_instance;
mutex Singleton::m_mutex;
2.双检测锁
同一时刻可能有多个线程尝试拿到锁,但只有第一个线程会拿到锁,后面的线程拿不到锁会阻塞,问题是,其他阻塞的线程总有拿到锁的一天,那么这些就都会进入if里面执行里面的语句。
解决方法很简单,只需要在if里面在判断一次指针是否已经分配内存即可。
class Singleton
{
public:
static Singleton* get_instance()
{
if(!m_instance)
{
lock_guard<mutex> lg(m_mutex);
if(m_instance) return m_instance;
m_instance = new Singleton();
}
return m_instance;
}
private:
static Singleton* m_instance;
static mutex m_mutex;
Singleton(){}
~Singleton(){}
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
};
Singleton* Singleton::m_instance;
mutex Singleton::m_mutex;
3.避免指令重排
指令重排详见:指令重排 、 内存顺序
简单来说,在if语句里,为了优化 *** 作,编译器会将语句执行顺序进行重新排序,比如上面的代码,申请堆内存放在读取if中m_instance指针的前面。
这是我们不愿意看到的,为了解决这一问题,我们引入了atomic(C++11)解决。
在初始化时,构造函数参数为空,内存排序属性默认为memory_order_seq_cst。简单来说我初始化了static atomic
,我先后执行了读取和赋值 *** 作,这两个的 *** 作的先后顺序是不会被指令重排优化的。
class Singleton
{
public:
static Singleton* get_instance()
{
Singleton* temp = m_instance.load();
if(!temp)
{
lock_guard<mutex> lg(m_mutex);
temp = m_instance.load();
if(temp) return temp;
m_instance.store(temp = new Singleton());
}
return temp;
}
private:
static mutex m_mutex;
static atomic<Singleton*> m_instance;
Singleton(){}
~Singleton(){}
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
};
mutex Singleton::m_mutex;
atomic<Singleton*> Singleton::m_instance;
至此,我们就实现了一个线程安全的懒汉单例模式。
C++11新特性C++11有一个新特性,那就是在函数中static变量的初始化是线程安全的。
那么实现一个线程安全的懒汉单例模式就简单很多了。
class Singleton
{
public:
static Singleton* get_instance()
{
static Singleton* m_instance = new Singleton();
return m_instance;
}
private:
Singleton(){}
~Singleton(){}
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
};
参考资料
CSDN:指令重排
cppreference:内存顺序
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)