对象性能模式
- 继承的内存代价很小
- 虚函数的内存代价很多。某些会有倍乘效应(比如本来可以做一次,但是用了虚函数要做一万次)
单例模式
单例模式的类声明
class Singleton{
private:
//对构造函数放在private区域防止对象被创建
Singleton();
//拷贝构造函数放在private区域防止对象被创建
Singleton(const Singleton& other);
public:
static Singleton* getInstance();
static Singleton* m_instance;
};
Singleton* Singleton::m_instance=nullptr;
线程非安全版本的单例模式实现代码
//线程非安全版本
Singleton* Singleton::getInstance() {
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
如果两个线程都进入该函数,则此时判断m_instance的值都为nullptr,因此两个线程都进入条件语句中new出来一个单例模式的实例。
线程安全版本,但锁的代价过高
读操作是不需要加锁的,但是这种方式会因为对读操作加锁来产生浪费
Singleton* Singleton::getInstance() {
Lock lock; //获取锁
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
双检查锁,但由于内存读写reorder不安全,会导致双检查锁的失效
先来说一下双锁的妙处
第一个锁可以避免读操作时的加锁行为带来的损耗,因为——读操作时,m_instance不为空。
第二个锁用于防止多产生实例化对象。因为——在加锁之前,两个线程因为都判断m_instance为空而进入到条件语句中。即便有一个线程先加锁了,生成了一个实例化对象,那么另一个线程也进来了,等第一个线程释放锁之后,就会去执行创建,从而多创建出一个对象。
因此需要双锁来确保只创建一个对象。
Singleton* Singleton::getInstance() {
//避免读操作的加锁损耗
if(m_instance==nullptr){
Lock lock;
//为了防止在加锁之前,两个线程都判断m_instance为空,而进入if判断中
//所以加锁之后再判断一次
if (m_instance == nullptr) {
m_instance = new Singleton();
}
}
return m_instance;
}
但是双锁法不能用,因为内存读写reorder会导致双锁失效。原因如下:
什么是reorder——代码都有指令序列,我们都认为代码会按照指令序列去执行。但是到了汇编层,也就是到了指令层次(我们知道,线程会在指令层抢时间片)
对于上侧代码中的创建实例动作,会分为三步
m_instance = new Singleton();
- 1.分配内存
- 2.调用构造器,对内存进行初始化
- 3.把指向那块内存的指针返回给m_instance
reorder之后很可能会变成先分配内存、再把指针给m_instance,然后在执行构造器
会出现的问题:
reorder之后,先分配内存,然后把内存分配给m_instance,此时m_instance就不是null了。一个线程进来发现m_instance不是null,就直接执行
return m_instance;
获取了对象实例,但是此时还没执行构造器,所以线程此时获取的是一块没被赋值的原生内存,这会导致错误发生
因此这个不能用,出错概率很高
C++ 11版本之后的跨平台实现 (volatile)
//C++ 11版本之后的跨平台实现 (volatile)
//volatile声明是告诉编译器,不能对下面的执行过程进行reorder的
//通过atomic声明一个原子对象
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;
Singleton* Singleton::getInstance() {
Singleton* tmp = m_instance.load(std::memory_order_relaxed);
//atomic_thread_fence是对内存reorder行为的屏障
//确保temp不会被reorder
std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(m_mutex);
tmp = m_instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton;
std::atomic_thread_fence(std::memory_order_release);//释放内存fence
m_instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}