[c/c++] c++11单例模式的最佳实现

分类:

单例模式分为懒汉和饿汉两种。饿汉式的写法比较固定也没有风险,懒汉式的写法比较多变且存在各种潜在的风险。

饿汉:

写法为:

A.h
----
class A
{
public:
  static A* getA();

private:
  static A* _instance;
};


A.cpp
----
#include "A.h"
A* A::_instance = new A();

A* A::getA();
{
  return _instance;
}

懒汉:

单锁单check写法:

A.h
----

class A
{
public:
  static A* getA()
  {
    std::lock_guard<std::mutex> lk(_instance_lk);
    if(nullptr == _instance){
      _instance = new A();
    }
    return _instance;
  }

private:
  static std::mutex _instance_lk;
  static A* _instance;
};

A.cpp
----

std::mutex A::_instance_lk;
A* A::_instance = nullptr;

这种写法能够保证绝对的线程安全性,但是性能会有很大损失,因为每次 getA() 都一定会加锁。而单例模式恰恰是仅在第一次才会创建实例,也就是说只有第一次才需要加锁,后面的所有加锁都是不必要的。因此便有双验证写法。

单锁双check粗糙写法(有缺陷):

A.h
----

class A
{
public:
  static A* getA()
  {
    if(nullptr == _instance){
      std::lock_guard<std::mutex> lk(_instance_lk);
      if(nullptr == _instance){
        _instance = new A();
      }
    }
    return _instance;
  }

private:
  static A* _instance;
  static std::mutex _instance_lk;
};

A.cpp
----

A* A::_instance = nullptr;
std::mutex A::_instance_lk;

单锁双check写法的初衷是解决单锁单check写法的效率问题,但是很不幸会引入side effect。针对上面的代码做如下假设:

1)线程1分别通过了第一次验证,加锁,第二次验证;

2)线程1开始调用 new 来创建实例,实例创建的过程比较缓慢

3)线程2抢占到CPU,打断了2)的实例创建操作,这个时候实际上 _instance 已经被赋予某个地址了。因此线程2会直接返回。

new 会做两个动作,一个是分配内存空间,一个是初始化类实例,分配好的内存空间会第一时间赋值给指针 _instance, 因此这个时候 _instance 实际上指向了一个尚未完全初始化完成的内存地址。

4)由于线程2跳过了后续的加锁和第二次判断,返回了一个并没有完全初始化完毕的实例,所以这会导致各种不确定行为

c++11 写法:

A.h

class A
{
public:
  static A& getA();
}

A.cpp

A& A::getA()
{
  static A instance;
  return instance;
}

 这种写法要求c++ 11编译,同时基于不同的编译器可能会有些许差别。不过现在距离c++ 11标准已经有很长时间了,相信基本上所有的编译器都已经完美地修复了这个问题,因此可以放心使用。

注意,这里只能返回引用,因为c++ 11只保证 static 变量的初始化是不可重入的。所以如果希望得到指针的话,还是需要使用单锁双check的方法,下面介绍改善版的单锁双check。

单锁双check安全写法 1:

A.h
----

class A
{
public:
  static A* getA()
  {
    A* tmp = _instance.load(std::memory_order_acquire);
    if(nullptr == _instance)
    {  
      std::lock_guard<std::mutex> lk(_instance_lk);
      tmp = _instance.load(std::memory_order_relax);
      if(nullptr == tmp){
        tmp= new A();
        _instance.store(tmp, std::memory_order_release);
      }
    }
    return tmp;
  }

private:
  static std::atomic<A*> _instance;
  static std::mutex _instance_lk;
};


A.cpp
----

std::atomic<A*> A::_instance;
std::mutex A::_instance_lk;

这种写法需要使用到 c++ 11 的 atomic 。

单锁双check安全写法 其他:

Double-Checked Locking is Fixed In C++11

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值