自学内容网 自学内容网

C++单例模式的实现

很多场景下都可以用到单例模式作为设计模式,比如:线程池类,服务器类(待实现挖坑),内存池类(待实现挖坑),

单例模式的两种实现方式:饿汉模式和懒汉模式

饿汉模式的缺点如下:

        1. 由于编译时需要初始化静态变量,导致程序启动变慢

        2. 如果有两个单例模式设计的类,而且其中一个依赖另一个,但是编译器并不确定哪个类的先初始化,此时有可能导致编译错误

饿汉模式

        在编译阶段就把单例创建好,后面的程序都是在这个单例已经存在的前提下开始运行。饿汉模式的实现框架相对固定,将静态变量作为单例类的成员变量,类内声明,类外初始化。

饿汉模式的优点:在多线程场景中,由于在进程启动前就已经有一个对象,那么多线程获取单例对象不需要加锁,有效的避免锁资源的竞争,提高性能

饿汉模式的缺点:在编译阶段需要较多的资源,可能导致进程启动较慢

其一般的设计框架的代码如下所示

class Singleton {
private:
    Singleton()
        :_data(99)
    {
        /*构造函数的逻辑*/
    }

    ~Singleton()
    {
        /*析构函数的逻辑*/
    }

    static Singleton tpins;
    int _data;
public:
    Singleton& operator=(const Singleton&) = delete;
    Singleton(const Singleton&) = delete;
    static Singleton& getInstance()
    {
        return tpins;
    }
};

Singleton Singleton::tpins;

懒汉模式

        在运行阶段才把单例创建好,后面的程序都是在这个创建好的单例的基础上继续进行,懒汉模式在C++11标准诞生之前和之后有两种实现方式,下面分别用代码实现

第一种:在C++11标准诞生之前实现单例模式的线程池

(注意pthread库需要重新配置)

class LockGuard{
public:
    LockGuard(pthread_mutex_t* pmutex)
    {
        _pmutex = pmutex;
        pthread_mutex_lock(_pmutex);
    }
    ~LockGuard()
    {
        pthread_mutex_unlock(_pmutex);
    }
private:
    pthread_mutex_t* _pmutex;
};

template <typename T>
class threadPool{
public:
    static threadPool<T>* getInstance(){
        if(ptpins == nullptr)
        {
            LockGuard lg(&mutexforsingleModel);
            if(ptpins == nullptr)
            {
                ptpins = new threadPool<T>;
                /*执行其他线程池需要的逻辑*/
            }
        }
        return ptpins;
    }
private:
    static threadPool<T>* ptpins;
    static pthread_mutex_t mutexforsingleModel;
    int threadNum;
    threadPool(const threadPool<T>&) = delete;
    threadPool<T>& operator=(const threadPool<T>&) = delete;
    threadPool()
        :threadNum(8)
    {
        /*构造函数的逻辑*/
    }
};

template <typename T>
threadPool<T>* threadPool<T>::ptpins = nullptr;
template <typename T>
pthread_mutex_t threadPool<T>::mutexforsingleModel = PTHREAD_MUTEX_INITIALIZER; 

上面的程序中有几个点值得说明

一、两次检查当前的线程池指针是否为空的作用

        外层为空检查:

        保证第一次创建线程池时,发现 ptpins == nullptr 成立的线程都会去申请锁资源

        只有那个申请锁成功的线程能从 LockGuard lg(&mutexforsingleModel); 这句话之后继续执行

        内层为空检查:

        只有申请锁成功的线程能够发现 ptpins == nullptr 依然成立,然后去堆上开辟空间

        开辟空间之后就会释放锁,其他线程即使在这之后成功申请到锁,也只能直接返回 ptpins

二、锁的设计有多种方式

        锁这个对象是线程池一个静态成员变量,

        方式一:如下代码,pthread_mutex_lock 和 pthread_mutex_unlock 都需要使用锁的指针作为参数

class LockGuard{
public:
    LockGuard(pthread_mutex_t* pmutex)
    {
        _pmutex = pmutex;
        pthread_mutex_lock(_pmutex);
    }
    ~LockGuard()
    {
        pthread_mutex_unlock(_pmutex);
    }
private:
    pthread_mutex_t* _pmutex;
};

template <typename T>
class threadPool{
public:
    /*获取单例的函数*/
private:
    static pthread_mutex_t mutexforsingleModel;
    /*其他成员变量*/
};

/*单例构造出来的静态变量*/
template <typename T>
pthread_mutex_t threadPool<T>::mutexforsingleModel = PTHREAD_MUTEX_INITIALIZER; 

        方式二:unique_lock<mutex> lock(_mtx); 

#include <mutex>
template <typename T>
class threadPool {
public:
    /*获取单例的函数*/
private:
    static mutex _mtx;
    /*其他成员变量*/
};

/*获取单例静态变量*/
template <typename T>
mutex threadPool<T>::_mtx;

三、必须把线程池对象在类外初始化成nullptr,

第二种:在C++11标准诞生之后实现单例模式的线程池

template <typename T>
class threadPool{
public:
    static threadPool<T>*& getInstance{
        static threadPool<T> threadPool;
        return &pthreadPool;
    }
    
private:
    threadPool(const threadPool<T>&) = delete;
    threadPool<T>& operator=(const threadPool<T>&) = delete;
    threadPool(int threadNum = defaultNum)
    {
        /*构造函数的逻辑*/
    }
    ~threadPool()
    {
        /*析构函数的逻辑*/
    }
};

C++11之后实现了只要是在静态的局部变量,第二次再次调用static threadPool<T> threadPool;这句话都不会调用构造函数,也就不会创建新的实例


原文地址:https://blog.csdn.net/weixin_46366676/article/details/142764006

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!