设计模式(对象性能模式)(单例模式)

对象性能模式

  • 继承的内存代价很小
  • 虚函数的内存代价很多。某些会有倍乘效应(比如本来可以做一次,但是用了虚函数要做一万次)

单例模式

单例模式的类声明

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;
}

总结

猜你喜欢

转载自blog.csdn.net/qq_29996285/article/details/88431192
今日推荐