如何构建线程安全的单例模式

构建线程安全的单例模式

饿汉直接声明

class Singleton {
public:
    static Singleton& GetInstance() {
        return _instance;
    }

private:
    Singleton() {}
    ~Singleton() {}

    Singleton(const Singleton&) = delete;
    Singleton(Singleton &&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    Singleton& operator=(const Singleton&&) = delete;

    static Singleton _instance;
};
  • 饿汉式地在类中声明单例实例,在类加载时初始化单例对象,通常是线程安全的。

双检查锁定

class Singleton {
public:
    static Singleton* GetInstance() {
        if(_instance == nullptr) {
            std::lock_guard<std::mutex> lock(_mutex);
            if(_instance == nullptr) {
                _instance = new Singleton();
                atexit(Destructor);
            }
        }
        return _instance;
    }

private:
    static void Destructor() {
        if(_instance != nullptr) {
            delete _instance;
            _instance = nullptr;
        }
    }
    Singleton() {}
    ~Singleton() {
        std::cout << "Singleton Destructor" << std::endl;
    }
    Singleton(const Singleton&) = delete;
    Singleton(Singleton &&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    Singleton& operator=(const Singleton&&) = delete;
    static Singleton* _instance;
    static std::mutex _mutex;
};
  • 声明为类内静态变量,该_instance对所有类的对象共享。因此通过GetInstance获得的单例对象始终为同一个对象。
  • GetInstance双检查+锁的模式避免了多个线程尝试获取单例的时候,由于其阻塞在互斥锁,当锁被其他线程释放后二次创建单例对象的问题
  • 当由一个或多个线程都来到lock_guard时,只有一个线程可以获得锁并创建单例。当锁被释放,二次检查会将其余的线程排除在外,避免二次创建对象。
  • 问题:多核多线程条件下由于CPU指令优化而导致的乱序问题

双检查锁定+内存屏障

class Singleton {
public:
    static Singleton* GetInstance() {
        Singleton *tmp = _instance.load(std::memory_order_relaxed);
        std::atomic_thread_fence(std::memory_order_acquire);
        if(tmp == nullptr) {
            std::lock_guard<std::mutex> lock(_mutex);
            tmp = _instance.load(std::memory_order_relaxed);
            if(tmp == nullptr) {
                tmp = new Singleton();
                std::atomic_thread_fence(std::memory_order_release);
                _instance.store(tmp, std::memory_order_relaxed);
                atexit(Destructor);
            }
        }
        return tmp;
    }

private:
    static void Destructor() {
        Singleton* tmp = _instance.load(std::memory_order_relaxed);
        if(tmp != nullptr) {
            delete tmp;
        }
    }
    Singleton() {}
    ~Singleton() {
        std::cout << "Singleton Destructor" << std::endl;
    }
    Singleton(const Singleton&) = delete;
    Singleton(Singleton &&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    Singleton& operator=(const Singleton&&) = delete;
    static std::atomic<Singleton*> _instance;
    static std::mutex _mutex;
};
  • 上面的代码中我们借助了内存屏障,并手动定义内存序列来创建一个多线程安全的单例
  • 由于_instance被定义为原子变量,我们用load加载它的值并使用memory_order_relaxed内存顺序模型
    • memory_order_relaxed:放松的内存序列规则,不对其他任何读写操作进行约束,只保证本次操作的原子性
  • 随后建立内存屏障,规定内存顺序为memory_order_acquire
    • memory_order_acquire:严格的内存序列规则,当前加载操作之前的所有读取和写入操作都不能被重排序到这个加载操作之后。
  • 执行双检查锁定,构造单例
  • 建立内存屏障,规定内存顺序为memory_order_release
    • memory_order_release:严格的内存序列规则,当前写操作之后的所有写入操作不能被重排序到该操作之前。
  • 和atomic_thread_fence搭配,可以粗暴地理解为,fence建立的acquire和release屏障间的操作顺序不能被编译器优化,需按照代码顺序严格执行。

利用C++11特性:静态局部变量

class Singleton {
public:
    static Singleton* GetInstance() {
        static Singleton instnace;
        return &instance;
    }

private:
    Singleton() {}
    ~Singleton() {
        std::cout << "Singleton Destructor" << std::endl;
    }
    Singleton(const Singleton&) = delete;
    Singleton(Singleton &&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    Singleton& operator=(const Singleton&&) = delete;
};
  • 借助C++11静态局部变量线程安全的特性,我们可以声明如上的简洁代码版本
  • 懒汉模式:只会在第一次调用GetInstance运行到这里时才会创建单例对象
  • 单例对象存储在静态存储区,删除时会自动调用单例的析构函数,无需额外处理

更泛用的方法:模板编程封装静态类

template <typename T>
class Singleton {
public:
    static T* GetInstance() {
        static T instnace;
        return &instance;
    }
protected:
	Singleton() {}
    ~Singleton() {
        std::cout << "Singleton Destructor" << std::endl;
    }
private:
    Singleton(const Singleton&) = delete;
    Singleton(Singleton &&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    Singleton& operator=(const Singleton&&) = delete;
};

class OtherSingletonClass : public Singleton<OtherSingletonClass> {
		friend class Singleton<OtherSingletonClass>;
public:
		// define your class other func here...
private:
		OtherSingletonClass(){}
		~OtherSingletonClass(){
				std::cout << "OtherSingletonClass Destructor" << std::endl;
		}
};

为什么模板类变成了protected而不是private?

  • 由于单例不允许在外部调用构造和析构函数,从而自己创建/销毁类的对象,因此才被称为单例。
  • 模板类由于涉及到类的继承,需要区分外部和子类调用函数的权限。protected对子类开放函数调用权限,同时拒绝外部调用,合乎上一条规则。

模板类能调用子类构造函数吗?

  • 不能,所以在子类中添加了友元函数定义,声明基类是子类的朋友,才能访问子类的构造和析构。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值