美文网首页
创建型模式 - 单例模式

创建型模式 - 单例模式

作者: GOGOYAO | 来源:发表于2019-07-28 16:14 被阅读0次

[TOC]

参考

你真的知道怎么写Singleton吗?
超全的设计模式简介(45种)
design-patterns-for-humans

1. 常见的Singleton写法

/* singleton.h */
class Singleton {
public:
    static Singleton *getInstance();
private:
    Singleton() {}
    ~Singleton() {}
private:
    static Singleton *singleton;
};

/* singleton.cpp */
Singleton *Singleton::singleton = NULL;
Singleton *Singleton::getInstance() {
    if (singleton == NULL)
        singleton = new Singleton();
    return singleton;
}

2. 大伙都知道的DCLP问题

以上的例子放在多线程环境下,可能会多个线程同时执行singleton = new Singleton(),造成那么一丢丢的内存泄漏。好的,我们加把锁:

/* singleton.h */
class Singleton {
public:
    static Singleton *getInstance();
private:
    Singleton() {}
    ~Singleton() {}
private:
    static Singleton *singleton;
    static pthread_mutex_t mutex;
};

/* singleton.cpp */
Singleton *Singleton::singleton = NULL;
pthread_mutex_t Singleton::mutex = PTHREAD_MUTEX_INITIALIZER;
Singleton *Singleton::getInstance() {
    if (singleton == NULL) {
        pthread_mutex_lock(&mutex);
        singleton = new Singleton();
        pthread_mutex_unlock(&mutex);
    }
    return singleton;
}

这样同样会有一个问题,可能多个线程同时等待mutex,然后singleton = new Singleton()同样会被多次调用,造成那么一丢丢的内存泄漏。好的,我们在锁里再加一层判断:

/* singleton.cpp */
Singleton *Singleton::singleton = NULL;
pthread_mutex_t Singleton::mutex = PTHREAD_MUTEX_INITIALIZER;
Singleton *Singleton::getInstance() {
    if (singleton == NULL) {
        pthread_mutex_lock(&mutex);
        if (singleton == NULL)
            singleton = new Singleton();
        pthread_mutex_unlock(&mutex);
    }
    return singleton;
}

干得不错,但是线程的多核CPU架构,多个CPU的cache line存在一个可见性问题,再加上new操作符可能被指令重排,所以运行在不同的CPU core上的多个线程仍然可能同时调用singleton = new Singleton(),造成那么一丢丢的内存泄漏。好的,我们用上memory model:

/* singleton.cpp */
Singleton *Singleton::singleton = NULL;
pthread_mutex_t Singleton::mutex = PTHREAD_MUTEX_INITIALIZER;
Singleton *Singleton::getInstance() {
    Singleton *temp = singleton.load(std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_acquire);
    if (temp == NULL) {
        pthread_mutex_lock(&mutex);
        temp = singleton.load(std::memory_order_relaxed);
        if (temp == NULL) {
            temp = new Singleton();
            std::atomic_thread_fence(std::memory_order_release);
            singleton.store(temp, std::memory_order_relaxed);
        }
        pthread_mutex_unlock(&mutex);
    }
    return temp;
}

以上便是DCLP(Double-Checked-Locking-Pattern)的最终版,你会发现写一个Singleton又是memory fence,又是atomic操作,简直费劲,下面我们来另一种写法。

3. 懒人Singleton写法

/* singleton.h */
class Singleton {
public:
    static Singleton *getInstance() {
        return &singleton;
    }
private:
    Singleton() {}
    ~Singleton() {}
private:
    static Singleton singleton;
};

/* singleton.cpp */
Singleton Singleton::singleton;

很优雅的规避了DCLP问题,并且多线程安全,为什么多线程安全呢,以下是cpp-reference上的一段解释:

Non-local variables

All non-local variables with static storage duration are initialized as part of program startup, before the execution of the main function begins (unless deferred, see below). All variables with thread-local storage duration are initialized as part of thread launch, sequenced-before the execution of the thread function begins.

类里面的static成员是全局的,会在编译的时候存在.bss段,在程序启动也就是在进入到_start后call main之前,进行初始化。但是呢,这种写法会存在一些隐患。

3.1. static initialization order fiasco

假设我有另一个同样的Singleton类SingletonB:

class SingletonB {
private:
    SingletonB() {
        Singleton *singleton = Singleton::getInstance();
        singleton->do_whatever();
    }
}

假定SingletonB先于Singleton编译,由于SingletonB初始化的时候Singleton没有初始化,所以程序在call main之前就会crash。

3.2. 怎么解决

解决方案很简单,将static Singleton singleton;由global scope转为function scope。

/* singleton.h */
class Singleton {
public:
    static Singleton *getInstance() {
        static Singleton singleton;
        return &singleton;
    }
private:
    Singleton() {}
    ~Singleton() {}
};  
/* 没有singleton.cpp */

这样只有在第一次调用Singlton::getInstance()才会初始化singleton。而且,该解决方案在C++11的前提下是线程安全的,以下是cpp-reference的解释:

If multiple threads attempt to initialize the same static local variable concurrently, the initialization occurs exactly once (similar behavior can be obtained for arbitrary functions with std::call_once).

Note: usual implementations of this feature use variants of the double-checked locking pattern, which reduces runtime overhead for already-initialized local statics to a single non-atomic boolean comparison. (since C++11)

这个是我在StackOverflow上抛出的一个问题

相关文章

  • 单例模式

    单例 单例模式,是一种设计模式,属于创建型设计模式,还有一种创建型设计模式,工厂模式。设计模式总共有23种,三大类...

  • 开发之设计模式-单例模式

    设计模式 设计模式分为三大类:创建型、结构型、行为型在Java中有24中设计模式 创建型:单例 1、为什么用单例模...

  • 【设计模式】创建型设计模式汇总

    创建型设计模式汇总 1. 单例模式 1.1 单例模式的定义 一个类只允许创建一个对象或实例。 1.2 单例模式的作...

  • 23种设计模式学习总结

    创建型设计模式 主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码合使用代码。 单例模式 单例模式用来...

  • 2.架构设计(单例模式设计)

    1.设计模式分为三个类 创建型 结构型 行为型 2.创建型:单例模式 为什么用单例模式?如果你看到这个问题,你怎么...

  • Python 之单例模式

    简介:单例模式(Singleton Pattern) 是最简单的设计模式之一,属于创建型的设计模式。单例模式涉及到...

  • PHP常用设计模式

    # 创建型 单例模式 工厂模式 工厂抽象模式 原型模式 建造者模式 # 结构型 # 行为型 # 3.注册模式 # ...

  • 设计模式分类

    经典23种设计模式: 创建型设计模式: Singleton Pattern(单例模式) PrototypePatt...

  • PHP设计模式—创建型设计模式

    ** 创建型设计模式 **: 单例模式(Singleton Pattern) 工厂方法模式(Factor Patt...

  • S4. 单例模式

    单例模式(Singleton) 介绍 单例模式是创建型设计模式,即用于创建对象的设计。其能够保证当前系统仅存在一个...

网友评论

      本文标题:创建型模式 - 单例模式

      本文链接:https://www.haomeiwen.com/subject/sohprctx.html