懒汉单例模式出现的线程安全问题(C++)

Posted 顾文繁

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了懒汉单例模式出现的线程安全问题(C++)相关的知识,希望对你有一定的参考价值。

在懒汉模式中,由于存在一个并没有被实例话的静态指针,在多线程环境中,导致在静态获取实例方法/函数时,会有多个线程进入到该代码进行实例对象(线程安全问题),下面展示存在线程安全的懒汉单例模式代码:

Singleton.h

class Singleton {
public:
    static Singleton* instance();
    void show();
private:
    static Singleton* singleton;
    static int num;
};

Singleton.cpp

#include "Singleton.h"

Singleton* Singleton::singleton = nullptr;
int Singleton::num = 0;

Singleton* Singleton::instance(){
    //this_thread::sleep_for(chrono::seconds(1));
    if(singleton == nullptr){
        //位置1
        singleton = new Singleton();
        num++;
    }
    return singleton;
}

void Singleton::show(){
    cout << "num : " << num << endl;
}

在上述Singleton.cpp文件中的 “位置1” 处,当有多个线程同时判断singleton为nullptr时,,每个线程都会在堆空间申请一个实例对象,因此这里线程获取到的实例对象不一致。

解决上述问题一种方法就是加锁。下述给出加锁的线程安全instance代码:

#include "Singleton.h"

Singleton* Singleton::singleton = nullptr;
int Singleton::num = 0;
mutex Singleton::m;

Singleton* Singleton::instance(){
    //this_thread::sleep_for(chrono::seconds(1));
    m.lock();
        if(singleton == nullptr){
            singleton = new Singleton();
            num++;
        }
    m.unlock();
    return singleton;
}

void Singleton::show(){
    cout << "num : " << num << endl;
}

在上述代码中,在判断实例对象是否是nullptr时,进行加锁,一个线程进行加锁之后,其他线程不能访问后面的资源转而等待锁,当锁释放时,其他线程之间又进行资源竞争。这样便保证了只有一个实例存在。But,这样存在一个又存在严重的问题,每次获取实例时,都要进行加锁,造成严重的性能问题。如何避免这个问题?我们使用双层对象判断。下面给出代码及解释。

#include "Singleton.h"

Singleton* Singleton::singleton = nullptr;
int Singleton::num = 0;
mutex Singleton::m;

Singleton* Singleton::instance(){
    //this_thread::sleep_for(chrono::seconds(1));
    if(singleton == nullptr){
        m.lock();
        if(singleton == nullptr) {
            singleton = new Singleton();
            num++;
        }
        m.unlock();
    }
    return singleton;
}

void Singleton::show(){
    cout << "num : " << num << endl;
}

上述的代码首先,判断singleton是否为nullptr,如果是,在多线程环境下,其中一个线程获得了资源进行加锁,进行实例对象,其他线程可能会被锁在第一层singleton==nullptr的下面进行等待,获得锁之后singleton已经不为nullptr了,则不会进行初始化,这样便保证了只有一个实例的情况下,还能获取良好的性能。

综上所述,只停留在理论的层面,下面进行多线程的实验。

首先我们建立线程回调函数,显示num的数量。

void show_instance_info(){
    Singleton::instance()->show();
}

在使用C++线程创建5个线程。

void test_singleton(){
    thread t1(show_instance_info);
    thread t2(show_instance_info);
    thread t3(show_instance_info);
    thread t4(show_instance_info);
    thread t5(show_instance_info);
    t1.join();
    t2.join();
    t3.join();
    t4.join();
    t5.join();
}

 

1.测试执行线程不安全的代码结果:

可以看到多次执行会有不同的结果,有多个对象进行了实例化。

1.测试执行线程安全的代码结果:

 

 

不管试验多少次,num总是1。 

以上是关于懒汉单例模式出现的线程安全问题(C++)的主要内容,如果未能解决你的问题,请参考以下文章

C++的单例模式与线程安全单例模式(懒汉/饿汉)

C++单例模式的实现(懒汉式饿汉式)

C++单例模式的实现(懒汉式饿汉式)

多线程 实现单例模式 ( 饿汉懒汉 ) 实现线程安全的单例模式 (双重效验锁)

单例设计模式之懒汉式(线程安全)

一文唬住所有面试官:懒汉式单例模式中的线程安全问题