单例模式-懒汉式及其线程安全问题

所谓懒汉,讲的是对象的构造是在第一次调用获取对象接口时才进行构造的

class Singleton{
public:
    //非可重入函数
    static Singleton*GetInstance(){
        if(instance == nullptr){
            instance = new Singleton();
        }
        return instance;
    }
private:
    static Singleton *instance;
    Singleton(){}
    Singleton(const Singleton&) = delete;
    void operator=(const Singleton&) = delete;
};
Singleton *Singleton::instance = nullptr;

单独将获取实例的接口拿出来分析,为什么不是线程安全的:

static Singleton*GetInstance(){
       if(instance == nullptr){
           instance = new Singleton();
       }
       return instance;
  }

上述instance = new Singleton(); 在宏观上看,整体可以划分为三个步骤:

  1. 调用malloc给对象分配内存;
  2. 调用对象的构造函数在内存上进行初始化工作;
  3. 将对象的地址赋值给instance.

假设这个过程,有两个线程执行,t1执行完1,2,时间片到了,被调度出去,此时t2来执行,一口气完成1,2,3,那么你觉得两个线程拿到的对象还是同一块内存上的对象吗? 当然不是。

从汇编指令上来讲步骤更多更复杂,所以我们需要借用锁来保证这个过程的正确性:

1. 最直接了当的加锁方式:不注重锁的粒度,影响性能

std::mutex mt;

class Singleton{
public:
    static Singleton*GetInstance(){
        std::lock_guard<std::mutex> guard(mt); //锁的粒度太大
        if(instance == nullptr){
            instance = new Singleton();
        }
        return instance;
    }
private:
    static Singleton *instance;
    Singleton(){}
    Singleton(const Singleton&) = delete;
    void operator=(const Singleton&) = delete;
};
Singleton *Singleton::instance = nullptr;

假设该单例对象的构造函数会进行大量的初始化工作,我们将锁加在这里,粒度过大,会影响整体的性能,不妥。

2.注重锁的粒度,“锁+双重判断”:


std::mutex mt;

class Singleton{
public:
    static Singleton*GetInstance(){
        if(instance == nullptr){   //1重判断
            std::lock_guard<std::mutex> guard(mt); //锁
            if(instance == nullptr){ //2重判断
                instance = new Singleton();
            }
        }
        return instance;
    }
private:
    static Singleton *volatile instance;
    Singleton(){}
    Singleton(const Singleton&) = delete;
    void operator=(const Singleton&) = delete;
};
Singleton  *volatile Singleton:: instance = nullptr;

并且最好使用volatile 去修饰指针变量,保证线程不会缓存一份指针变量,引起误差。
总结:
double check + lock + volatile.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值