zoukankan      html  css  js  c++  java
  • 创建型模式 单例模式

    创建型模式 单例模式

    /**
     * 创建型模式 单例模式 懒汉式
     * GoF对单例模式的定义是:保证一个类、只有一个实例存在,同时提供能对该实例加以访问的全局访问方法。
     *
     * 实现单例步骤常用步骤
     * a) 构造函数私有化
     * b) 提供一个全局的静态方法(全局访问点)
     * c) 在类中定义一个静态指针,指向本类的变量的静态变量指针
     *
     */
    
    #include <iostream>
    
    class Singelton
    {
    private:
        static Singelton * m_pls;
    
        Singelton() // 构造函数不是线程安全函数
        {
            std::cout << "Singelton 构造函数执行" << std::endl;
        }
    public:
        static Singelton *getInstance()
        {
            if (m_pls == nullptr) // 只有在使用的时候,才去创建对象。 1 每次获取实例都要判断 2 多线程会有问题 
            {
                m_pls = new Singelton;
            }
            return m_pls;
        }
    
        static Singelton *FreeInstance()
        {
            if (m_pls != nullptr)
            {
                delete m_pls;
                m_pls = nullptr;
            }
            return m_pls;
        }
    };
    
    Singelton * Singelton::m_pls = nullptr;
    
    
    void mytest()
    {
        Singelton * p1 = Singelton::getInstance();
        Singelton * p2 = Singelton::getInstance();
        if (p1 == p2)
        {
            std::cout << "是同一个对象" << std::endl;
        }
        else
        {
            std::cout << "不是同一个对象" << std::endl;
        }
        Singelton::FreeInstance();
    
        return;
    }
    
    
    int main()
    {
        mytest();
    
        system("pause");
        return 0;
    }
    /**
     * 创建型模式 单例模式 饿汉式
     * GoF对单例模式的定义是:保证一个类、只有一个实例存在,同时提供能对该实例加以访问的全局访问方法。
     *
     * 实现单例步骤常用步骤
     * a) 构造函数私有化
     * b) 提供一个全局的静态方法(全局访问点)
     * c) 在类中定义一个静态指针,指向本类的变量的静态变量指针
     *
     */
    
    #include <iostream>
    
    class Singelton
    {
    private:
        static Singelton * m_pls;
    
        Singelton() 
        {
            std::cout << "Singelton 构造函数执行" << std::endl;
        }
    public:
        static Singelton *getInstance()
        {
            return m_pls;
        }
    
        static Singelton *FreeInstance()
        {
            if (m_pls != nullptr)
            {
                delete m_pls;
                m_pls = nullptr;
            }
            return m_pls;
        }
    };
    
    // 饿汉式
    // 不管你创建不创建实例,均把实例new出来
    Singelton * Singelton::m_pls = new Singelton;
    
    
    void mytest()
    {
        Singelton * p1 = Singelton::getInstance();
        Singelton * p2 = Singelton::getInstance();
        if (p1 == p2)
        {
            std::cout << "是同一个对象" << std::endl;
        }
        else
        {
            std::cout << "不是同一个对象" << std::endl;
        }
        Singelton::FreeInstance();
    
        return;
    }
    
    
    int main()
    {
        mytest();
    
        system("pause");
        return 0;
    }

    ================

    来源 https://juejin.im/post/5d692773f265da03986c0832

    C++ 线程安全的单例模式总结

    什么是线程安全?

    在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。


    如何保证线程安全?

    1. 给共享的资源加把锁,保证每个资源变量每时每刻至多被一个线程占用。
    2. 让线程也拥有资源,不用去共享进程中的资源。如: 使用threadlocal可以为每个线程的维护一个私有的本地变量。

    什么是单例模式?

    单例模式指在整个系统生命周期里,保证一个类只能产生一个实例,确保该类的唯一性。

    单例模式分类

    单例模式可以分为懒汉式和饿汉式,两者之间的区别在于创建实例的时间不同:

    • 懒汉式:指系统运行中,实例并不存在,只有当需要使用该实例时,才会去创建并使用实例。(这种方式要考虑线程安全,需要加 双重检查锁定模式(DCLP) 
    • 饿汉式:指系统一运行,就初始化创建实例,当需要时,直接调用即可。(本身就线程安全,没有多线程的问题)

    单例类特点

    • 构造函数和析构函数为private类型,目的禁止外部构造和析构
    • 拷贝构造和赋值构造函数为private类型,目的是禁止外部拷贝和赋值,确保实例的唯一性
    • 类里有个获取实例的静态函数,可以全局访问

    01 普通懒汉式单例 ( 线程不安全 )

    ///////////////////  普通懒汉式实现 -- 线程不安全 //////////////////
    #include <iostream> // std::cout
    #include <mutex>    // std::mutex
    #include <pthread.h> // pthread_create
    
    class SingleInstance
    {
    
    public:
        // 获取单例对象
        static SingleInstance *GetInstance();
    
        // 释放单例,进程退出时调用
        static void deleteInstance();
    	
    	// 打印单例地址
        void Print();
    
    private:
    	// 将其构造和析构成为私有的, 禁止外部构造和析构
        SingleInstance();
        ~SingleInstance();
    
        // 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
        SingleInstance(const SingleInstance &signal);
        const SingleInstance &operator=(const SingleInstance &signal);
    
    private:
        // 唯一单例对象指针
        static SingleInstance *m_SingleInstance;
    };
    
    //初始化静态成员变量
    SingleInstance *SingleInstance::m_SingleInstance = NULL;
    
    SingleInstance* SingleInstance::GetInstance()
    {
    
    	if (m_SingleInstance == NULL)
    	{
    	    m_SingleInstance = new (std::nothrow) SingleInstance;  // 没有加锁是线程不安全的,当线程并发时会创建多个实例
    	}
    
        return m_SingleInstance;
    }
    
    void SingleInstance::deleteInstance()
    {
        if (m_SingleInstance)
        {
            delete m_SingleInstance;
            m_SingleInstance = NULL;
        }
    }
    
    void SingleInstance::Print()
    {
    	std::cout << "我的实例内存地址是:" << this << std::endl;
    }
    
    SingleInstance::SingleInstance()
    {
        std::cout << "构造函数" << std::endl;
    }
    
    SingleInstance::~SingleInstance()
    {
        std::cout << "析构函数" << std::endl;
    }
    ///////////////////  普通懒汉式实现 -- 线程不安全  //////////////////
    
    // 线程函数
    void *PrintHello(void *threadid)
    {
        // 主线程与子线程分离,两者相互不干涉,子线程结束同时子线程的资源自动回收
        pthread_detach(pthread_self());
    
        // 对传入的参数进行强制类型转换,由无类型指针变为整形数指针,然后再读取
        int tid = *((int *)threadid);
    
        std::cout << "Hi, 我是线程 ID:[" << tid << "]" << std::endl;
    
        // 打印实例地址
        SingleInstance::GetInstance()->Print();
    
        pthread_exit(NULL);
    }
    
    #define NUM_THREADS 5 // 线程个数
    
    int main(void)
    {
        pthread_t threads[NUM_THREADS] = {0};
        int indexes[NUM_THREADS] = {0}; // 用数组来保存i的值
    
        int ret = 0;
        int i = 0;
    
        std::cout << "main() : 开始 ... " << std::endl;
    
        for (i = 0; i < NUM_THREADS; i++)
        {
            std::cout << "main() : 创建线程:[" << i << "]" << std::endl;
            
            indexes[i] = i; //先保存i的值
    		
            // 传入的时候必须强制转换为void* 类型,即无类型指针
            ret = pthread_create(&threads[i], NULL, PrintHello, (void *)&(indexes[i]));
            if (ret)
            {
                std::cout << "Error:无法创建线程," << ret << std::endl;
                exit(-1);
            }
        }
    
        // 手动释放单实例的资源
        SingleInstance::deleteInstance();
        std::cout << "main() : 结束! " << std::endl;
    	
        return 0;
    }
    复制代码

    普通懒汉式单例运行结果:

    从运行结果可知,单例构造函数创建了两个个,内存地址分别为0x7f3c980008c00x7f3c900008c0,所以普通懒汉式单例只适合单进程不适合多线程,因为是线程不安全的。


    02 加锁的懒汉式单例 ( 线程安全 )

    ///////////////////  加锁的懒汉式实现  //////////////////
    class SingleInstance
    {
    
    public:
        // 获取单实例对象
        static SingleInstance *&GetInstance();
    
        //释放单实例,进程退出时调用
        static void deleteInstance();
    	
        // 打印实例地址
        void Print();
    
    private:
        // 将其构造和析构成为私有的, 禁止外部构造和析构
        SingleInstance();
        ~SingleInstance();
    
        // 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
        SingleInstance(const SingleInstance &signal);
        const SingleInstance &operator=(const SingleInstance &signal);
    
    private:
        // 唯一单实例对象指针
        static SingleInstance *m_SingleInstance;
        static std::mutex m_Mutex;
    };
    
    //初始化静态成员变量
    SingleInstance *SingleInstance::m_SingleInstance = NULL;
    std::mutex SingleInstance::m_Mutex;
    
    SingleInstance *&SingleInstance::GetInstance()
    {
    
        //  这里使用了两个 if判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,
        //  避免每次调用 GetInstance的方法都加锁,锁的开销毕竟还是有点大的。
        if (m_SingleInstance == NULL) 
        {
            std::unique_lock<std::mutex> lock(m_Mutex); // 加锁
            if (m_SingleInstance == NULL)
            {
                m_SingleInstance = new (std::nothrow) SingleInstance;
            }
        }
    
        return m_SingleInstance;
    }
    
    void SingleInstance::deleteInstance()
    {
        std::unique_lock<std::mutex> lock(m_Mutex); // 加锁
        if (m_SingleInstance)
        {
            delete m_SingleInstance;
            m_SingleInstance = NULL;
        }
    }
    
    void SingleInstance::Print()
    {
    	std::cout << "我的实例内存地址是:" << this << std::endl;
    }
    
    SingleInstance::SingleInstance()
    {
        std::cout << "构造函数" << std::endl;
    }
    
    SingleInstance::~SingleInstance()
    {
        std::cout << "析构函数" << std::endl;
    }
    ///////////////////  加锁的懒汉式实现  //////////////////
    复制代码

    加锁的懒汉式单例的运行结果:

    从运行结果可知,只创建了一个实例,内存地址是0x7f28b00008c0,所以加了互斥锁的普通懒汉式是线程安全的


    03 内部静态变量的懒汉单例(C++11 线程安全)

    ///////////////////  内部静态变量的懒汉实现  //////////////////
    class Single
    {
    
    public:
        // 获取单实例对象
        static Single &GetInstance();
    	
    	// 打印实例地址
        void Print();
    
    private:
        // 禁止外部构造
        Single();
    
        // 禁止外部析构
        ~Single();
    
        // 禁止外部复制构造
        Single(const Single &signal);
    
        // 禁止外部赋值操作
        const Single &operator=(const Single &signal);
    };
    
    Single &Single::GetInstance()
    {
        // 局部静态特性的方式实现单实例
        static Single signal;
        return signal;
    }
    
    void Single::Print()
    {
        std::cout << "我的实例内存地址是:" << this << std::endl;
    }
    
    Single::Single()
    {
        std::cout << "构造函数" << std::endl;
    }
    
    Single::~Single()
    {
        std::cout << "析构函数" << std::endl;
    }
    ///////////////////  内部静态变量的懒汉实现  //////////////////
    复制代码

    内部静态变量的懒汉单例的运行结果:

    -std=c++0x编译是使用了C++11的特性,在C++11内部静态变量的方式里是线程安全的,只创建了一次实例,内存地址是0x6016e8,这个方式非常推荐,实现的代码最少!

    [root@lincoding singleInstall]#g++  SingleInstance.cpp -o SingleInstance -lpthread -std=c++0x
    复制代码


    04 饿汉式单例 (本身就线程安全)

    ////////////////////////// 饿汉实现 /////////////////////
    class Singleton
    {
    public:
        // 获取单实例
        static Singleton* GetInstance();
    
        // 释放单实例,进程退出时调用
        static void deleteInstance();
        
        // 打印实例地址
        void Print();
    
    private:
        // 将其构造和析构成为私有的, 禁止外部构造和析构
        Singleton();
        ~Singleton();
    
        // 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
        Singleton(const Singleton &signal);
        const Singleton &operator=(const Singleton &signal);
    
    private:
        // 唯一单实例对象指针
        static Singleton *g_pSingleton;
    };
    
    // 代码一运行就初始化创建实例 ,本身就线程安全
    Singleton* Singleton::g_pSingleton = new (std::nothrow) Singleton;
    
    Singleton* Singleton::GetInstance()
    {
        return g_pSingleton;
    }
    
    void Singleton::deleteInstance()
    {
        if (g_pSingleton)
        {
            delete g_pSingleton;
            g_pSingleton = NULL;
        }
    }
    
    void Singleton::Print()
    {
        std::cout << "我的实例内存地址是:" << this << std::endl;
    }
    
    Singleton::Singleton()
    {
        std::cout << "构造函数" << std::endl;
    }
    
    Singleton::~Singleton()
    {
        std::cout << "析构函数" << std::endl;
    }
    ////////////////////////// 饿汉实现 /////////////////////
    复制代码

    饿汉式单例的运行结果:

    从运行结果可知,饿汉式在程序一开始就构造函数初始化了,所以本身就线程安全的


    特点与选择

    • 懒汉式是以时间换空间,适应于访问量较小时;推荐使用内部静态变量的懒汉单例,代码量少
    • 饿汉式是以空间换时间,适应于访问量较大时,或者线程比较多的的情况

    =================

    双重检查锁定模式(DCLP)在无锁编程方面是有点儿臭名昭著案例学术研究的味道。直到2004年,使用java开发并没有安全的方式来实现它。在c++11之前,使用便捷式c+开发并没有安全的方式来实现它。由于引起人们关注的缺点模式暴露在这些语言之中,人们开始写它。一组高调的java聚集在一起开发人员并签署了一项声明,题为:“双重检查锁定坏了”。在2004年斯科特 、梅尔斯和安德烈、亚历山发表了一篇文章,题为:“c+与双重检查锁定的危险”对于DCLP是什么?这两篇文章都是伟大的引物,为什么呢?在当时看来,这些语言都不足以实现它。

    在过去。java现在可以为修订内存模型,为thevolatileeyword注入新的语义,使得它尽可然安全实现DCLP.同样地,c+11有一个全新的内存模型和原子库使得各种各样的便捷式DCLP得以实现。c+11反过来启发Mintomic,一个小型图书馆,我今年早些时候发布的,这使得它尽可能的实现一些较旧的c/c++编译器以及DCLP.

    在这篇文章中,我将重点关注c++实现的DCLP.

    什么是双重检查锁定?

    假设你有一个类,它实现了著名的Singleton 模式,现在你想让它变得线程安全。显然的一个方法就是通过增加一个锁来保证互斥共享。这样的话,如果有两个线程同时调用了Singleton::getInstance,将只有其中之一会创建这个单例。

    1
    2
    3
    4
    Singleton* Singleton::getInstance() { Lock lock; // scope-based lock, released automatically when the function returns if (m_instance == NULL) {
            m_instance = new Singleton;
        } return m_instance;
    }

    这是完全合法的方法,但是一旦单例被创建,实际上就不再需要锁了。锁不一定慢,但是在高并发的条件下,不具有很好的伸缩性。

    双重检查锁定模式避免了在单例已经存在时候的锁定。不过如Meyers-Alexandrescu的论文所显示的,它并不简单。在那篇论文中,作者描述了几个有缺陷的用C++实现DCLP的尝试,并剖析了每种情况为什么是不安全的。最后,在第12页,他们给出了一个安全的实现,但是它依赖于非指定的,特定平台的内存屏障(memory barriers)

    (译注:内存屏障就是一种干预手段. 他们能保证处于内存屏障两边的内存操作满足部分有序)

    1
    2
    3
    4
    5
    6
    7
    8
    Singleton* Singleton::getInstance() {
        Singleton* tmp = m_instance; ... // insert memory barrier if (tmp == NULL) {
            Lock lock;
            tmp = m_instance; if (tmp == NULL) {
                tmp = new Singleton; ... // insert memory barrier m_instance = tmp;
            }
        } return tmp;
    }

    这里,我们可以发现双重检查锁定模式是由此得名的:在单例指针m_instance为NULL的时候,我们仅仅使用了一个锁,这个锁使偶然访问到该单例的第一组线程继续下去。而在锁的内部,m_instance被再次检查,这样就只有第一个线程可以创建这个单例了。

    这与可运行的实现非常相近。只是在突出显示的几行漏掉了某种内存屏障。在作者写这篇论文的时候,还没有填补此项空白的轻便的C/C++函数。现在,C++11已经有了。

    用 C++11 获得与释放屏障

    你可以用获得与释放屏障 安全的完成上述实现,在我以前的文章中我已经详细的解释过这个主题。不过,为了让代码真正的具有可移植性,你还必须要将m_instance包装成原子类型,并且用放松的原子操作(译注:即非原子操作)来操作它。这里给出的是结果代码,获取与释放屏障部分高亮了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    std::atomic<Singleton*> Singleton::m_instance;
    std::mutex Singleton::m_mutex;
     
    Singleton* Singleton::getInstance() {
        Singleton* tmp = m_instance.load(std::memory_order_relaxed); std::atomic_thread_fence(std::memory_order_acquire); 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); m_instance.store(tmp, std::memory_order_relaxed);
            }
        } return tmp;
    }

    即使是在多核系统上,它也可以令人信赖的工作,因为内存屏障在创建单例的线程与其后任何跳过这个锁的线程之间,创建了一种同步的关系。Singleton::m_instance充当警卫变量,而单例本身的内容充当有效载荷。

    所有那些有缺陷的DCLP实现都忽视了这一点:如果没有同步的关系,将无法保证第一个线程的所有写操作——特别是,那些在单例构造器中执行的写操作——可以对第二个线程可见,虽然m_instance指针本身是可见的!第一个线程具有的锁也对此无能为力,因为第二个线程不必获得任何锁,因此它能并发的运行。

    如果你想更深入的理解这些屏障为什么以及如何使得DCLP具有可信赖性,在我以前的文章中有一些背景信息,就像这个博客早前的文章一样。

    使用 Mintomic 屏障

    Mintomic 是一个小型的C语言的库,它提供了C++11原子库的一个功能子集,其中包含有获取与释放屏障,而且它是运行于更老的编译器之上的。Mintomic依赖于这样的假设 ,即C++11的内存模型——特殊的是,其中包括无中生有的存储 ——因为它不被更老的编译器支持,不过这已经是我们不通过C++11能做到的最佳程度了。记住这些东西可是若干年来我们在写多线程C++代码时的环境。无中生有的存储(Out-of-thin-air stores)已被时间证明是不流行的,而且好的编译器也基本上不会这么做。

    这里有一个DCLP的实现,就是用Mintomic来获取与释放屏障的。和前面使用C++11获取和释放屏障的例子比起来,它基本上是等效的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    mint_atomicPtr_t Singleton::m_instance = { 0 };
    mint_mutex_t Singleton::m_mutex;
     
    Singleton* Singleton::getInstance() {
        Singleton* tmp = (Singleton*) mint_load_ptr_relaxed(&m_instance); mint_thread_fence_acquire(); if (tmp == NULL) {
            mint_mutex_lock(&m_mutex);
            tmp = (Singleton*) mint_load_ptr_relaxed(&m_instance); if (tmp == NULL) {
                tmp = new Singleton; mint_thread_fence_release(); mint_store_ptr_relaxed(&m_instance, tmp);
            }
            mint_mutex_unlock(&m_mutex);
        } return tmp;
    }

    为了实现获取与释放屏障,Mintomic试图在所有它所支持的平台上,生成最有效的机器代码。举个例子,这里是Xbox 360上的机器代码结果,Xbox 360是基于PowerPC的。在这个平台上,单行的lwsync是一条最精简的指令,它既可以获取也可以释放屏障。

    如果启用了优化,之前基于C++11的例子也可以(理想情况下将会)生成完全相同的PowerPC机器代码。可惜的是,我并没有找来兼容C++11的PowerPC编译器来证明它。

    使用c++ 11低级排序约束

    C++11的获取与释放屏障可以正确的实现DCLP,而且应该能够针对当今大多数的多核设备,生成优化的机器代码(就像Mintomic做的那样),但是它们似乎不是非常时髦。在C++11中获得同等效果的首选方法,应该是使用基于低级排序约束的原子操作。正如我先前所说,一条写释放(write-release)可以同步于一条读获取(read-acquire)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    std::atomic<Singleton*> Singleton::m_instance;
    std::mutex Singleton::m_mutex;
     
    Singleton* Singleton::getInstance() { Singleton* tmp = m_instance.load(std::memory_order_acquire); 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; m_instance.store(tmp, std::memory_order_release); }
        } return tmp;
    }

    从技术上说,这种无锁的同步形式,比使用独立屏障的形式,要不那么严格;上面的操作只是意味着阻止它们自己周围的内存重新排序,这与独立的屏障不同,后者意味着阻止所有相邻的操作的特定类型的内存重排序。尽管如此,在x86/64, ARMv6/v7,以及 PowerPC架构上,对于这两种形式,可能的最好代码都是相同的。例如,在一篇早前文章中,我演示了在ARMv7编译器上,C++11低级排序约束是如何发送dmb指令的,而这也正是你在使用独立屏障时所期待的同样事情。

    这两种形式有可能会生成不同机器代码的一个平台是Itanium。Itanium可以使用一条单独的CPU指令,ld.acq来实现C++11的load(memory_order_acquire),并可以使用st.rel来实现store(tmp, memory_order_release)。我很想研究一下这些指令与独立屏障之间的性能差异,可是我找不到可用的Itanium机器。

    另一个这样的平台是最近出现的ARMv8架构。ARMv8提供了ldar和stlr指令,除了它们也增强了stlr指令以及任何后续的ldar指令之间的存储加载排序以外,其它的都与Itanium的ld.acq和st.rel指令很相似。事实上,ARMv8的这些新指令意在实现C++11的SC原子操作,这在后面会讲到。

    使用 C++11的顺序一致原子

    C++11提供了一种完全不同的方法来写无锁代码。(我们可以认为在某些特定的代码路径上DCLP是“无锁”的,因为并不是所有的线程都具有锁。)如果在所有原子库函数上,你忽略了可选的std::memory_order参数,那么默认值std::memory_order_seq_cst就会将所有的原子变量转变为顺序一致的(sequentially consistent) (SC)原子。通过SC原子,只要不存在数据竞争,整个算法就可以保证是顺序一致的。SC原子Java 5+中的volatile变量非常相似。

    这里是使用SC原子的一个DCLP实现。如之前所有例子一样,一旦单例被创建,第二行高亮将与第一行同步

    1
    2
    3
    4
    5
    6
    7
    8
    9
    std::atomic<Singleton*> Singleton::m_instance;
    std::mutex Singleton::m_mutex;
     
    Singleton* Singleton::getInstance() { Singleton* tmp = m_instance.load(); if (tmp == nullptr) {
            std::lock_guard<std::mutex> lock(m_mutex);
            tmp = m_instance.load(); if (tmp == nullptr) {
                tmp = new Singleton; m_instance.store(tmp); }
        } return tmp;
    }

    SC原子被认为可以使程序员更容易思考。其代价是生成的机器代码似乎比之前的例子效率要低。例如,这里有有一些关于上面代码清单的x64机器代码,由Clang 3.3在启用代码优化的条件下生成:

    由于我们使用了SC原子,保存到m_instance是由xchg指令实现的,在x64上它具有内存屏障作用。这比x64中DCLP实际需要的指令更强。只需一条简单的mov指令就可以做这项工作。不过这并不十分要紧,因为在单例首次创建的代码路径上,xchg指令只下发一次。

    另一方面,如果你给PowerPC 或 ARMv6/v7编译SC原子指令,你十有八九会得到糟糕的机器代码。其中的细节,请看Herb Sutter的atomic<> 武器说话,第 2部分的00:44:25 - 00:49:16段落。

    使用 C++11 的数据相关性排序

    在上面所有我给出的例子中,在创建单例的那个线程,与其后任何越过锁的线程之间,有一种同步的关系。警卫变量就是单例指针,有效载荷是单例自身的内容。在本例中,有效载荷被认为是警卫指针的一个相关性数据。

    人们后来发现,当存在数据相关性时,上面所有例子中都用到的读获取(read-acquire)操作,将极富杀伤力!我们用消费操作(consume operation)来替代它要好一点。消费操作很酷,因为它们消除了PowerPC中的一条lwsync指令,以及ARMv7中的一条dmb指令。在将来的一篇文章中,我将更多的谈论到有关数据相关性和消费操作的内容。

    使用C++11中的静态初始化器

    有些读者已经知道这篇文章的妙语:如果你想得到一个线程安全的实例,C++11不允许你跳过以上的所有步骤。你可以简单使用一个静态初始化器

    1
    2
    3
    Singleton& Singleton::getInstance() {
        static Singleton instance; return instance;
    }

    让我们回到6.7.6节查看C++11的标准:

    如果控制进入申明同时变量将被初始化的时候,那么并发执行将会等到初始化的完成。

    由编译器来临时代替实现的细节,DCLP明显是一个不错的选择。不能保证编译器将会使用DCLP,但一些(也许更多)却碰巧发生了。使用the-std=c++0x选项对ARM进行编译,生成了下面的一些机器码,这些机器码是由GCC 4.6生成的。

    由于单例创建于固定地址,为了同步的目的,编译器引进了一个独立的警卫变量。特别需要注意的是,在最初读到这个警卫变量之后,并没有现成的dmb指令可以用来获取内存屏障。警卫变量是指向单例的指针,因此编译器可以利用数据相关性,省略掉这种dmb指令。__cxa_guard_release对警卫变量执行了一个写释放(write-release)操作,这样只要警卫变量已设置,在读消费(read-consume)之前就建立了依赖顺序,就像前面所有例子里那样,基于对内存的重新排序,整个事情开始变得有弹性。

    如你所见,我们已伴随C++11走过了一段漫长的道路。双重检查锁定是一种稳定的模式,而且还远不止此!

    就个人而言,我常常想,如果是需要初始化一个单例,最好是在程序启动的时候做这个事情。但是显然DCLP可以拯救你于泥潭。而且在实际的使用中,你还可以用DCLP来将任意数值类型存储到一个无锁的哈希表。在以后的文章中会有更多关于它的论述。

    =============== End

  • 相关阅读:
    mysql,windows自动备份设置
    彻底搞清楚javascript中的require、import和export
    Spring Boot 打包报错Failed to execute goal org.apache.maven.plugins:mavenresourcesplugin:3.2.0
    Spring AOP 切点切面
    12.5M 30M 90M DEM免费下载!【转】
    JS 中的数组遍历方式效率比较[转]
    cesium加载CAD模型(.dwg)
    Cesium发布下一代3D Tiles规范预览
    cesium点击面高亮事件[转]
    MySQL 5.7及8.0版本数据库的root密码遗忘的解决办法
  • 原文地址:https://www.cnblogs.com/lsgxeva/p/7773741.html
Copyright © 2011-2022 走看看