当前位置: 首页 > article >正文

Linux -- 单例模式

目录

概念

主要特点

饿汉模式和懒汉模式

饿汉模式

懒汉模式

应用场景 

单例模式下的线程池

GetInstance 函数:

单例模式禁止赋值和拷贝: 

完整代码:


概念

单例模式是软件工程中的一种设计模式,属于创建型模式。它确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。

主要特点

  1. 唯一实例:整个应用程序能存在该类的一个实例
  2. 私有构造函数:防止其他对象使用new关键字直接实例化。
  3. 静态工厂方法:提供一个静态方法作为创建实例的全局访问点。
  4. 延迟初始化:有些实现方式会直到第一次使用时才创建实例,以节省资源。

单例模式的类禁止赋值或拷贝,否则破坏单例原则!!

饿汉模式和懒汉模式

饿汉模式和懒汉模式是实现单例模式的两种常见策略,它们主要区别在于何时创建单例实例。

饿汉模式

饿汉模式是在类加载时就立即创建单例对象。这种方式简单且线程安全,因为对象在类加载时就已经被创建,所以在多线程环境下不会出现两个线程同时创建对象的情况。缺点是如果这个对象在整个应用程序运行期间都不被使用,那么将造成资源浪费。

template <typename T>
class Singleton {
    static T data;
public:
    static T* GetInstance() 
    {
        return &data;
    }
};

懒汉模式

懒汉模式是延迟到真正需要该对象的时候才创建它。懒汉模式最核心的思想是 “ 延时加载 ”,从而能够优化服务器的启动速度。这种方式可以节省内存资源,因为它只会在第一次调用 getInstance 方法时创建对象。然而,这种实现方式不是线程安全的,因为在多线程环境中可能会导致多个实例被创建。为了保证线程安全,可以在 getInstance 方法中加入同步机制,但这样又会影响性能。在类中存在一个指针,可执行程序加载到内存时,只是初始化指针,并不会创建具体的对象,第一次调用getInstance 方法时才会 new 对象。

template <typename T>
class Singleton {
    static T* inst;//指针
public:
    static T* GetInstance() 
    {
        if (inst == NULL) //第一次调用
        {
            inst = new T();
        } 
        return inst;
    }
};

应用场景 

  • 如果你确定你的单例对象一定会被使用,或者它的初始化成本很小,那么可以选择饿汉模式;
  • 如果你希望延迟初始化以节省资源,并且能够妥善处理线程安全问题,那么懒汉模式可能更适合你。

单例模式下的线程池

GetInstance 函数:

双重判定空指针,可以避免不必要的锁竞争,提高性能。

static ThreadPool<T> *GetInstance()//静态函数
    {
        // 如果是第一个调用,则会创建单例
        if (_instance == nullptr)
        {
            //不在if外面加锁,是因为如果是第二次调用的话,不需要创建单例,不需要保护资源
            //只需要获取单例,所以只需要在第一次调用的时候加锁,既保证线程安全又降低加锁成本
            LockGuard lockguard(&_lock);

            //第二次判断,防止在阻塞等待锁的过程中已经由别的线程创建单例了
            //但由于本线程已经通过if判断了,导致再创建了一次实例
            if (_instance == nullptr)
            {
                _instance = new ThreadPool<T>();
                _instance->InitThreadPool();
                _instance->Start();

                LOG(DEBUG, "创建线程池单例成功");
            }
        }
        // 不是第一次调用,则直接获取单例,不需要重新创建
        else
        {
            LOG(DEBUG, "获取线程池单例成功");
        }
        return _instance;
    }

单例模式禁止赋值和拷贝: 

// 禁用赋值和拷贝
ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;
ThreadPool(const ThreadPool<T> &) = delete;

 

完整代码:

#pragma once

// 线程池的封装

#include <pthread.h>
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include "Thread.hpp"
#include "LockGuard.hpp"
#include "Log.hpp"
using namespace ThreadModule;

const static int gdefaultthreadnum = 10;
template <typename T>
class ThreadPool
{
private:
    void LockQueue()
    {
        pthread_mutex_lock(&_mutex);
    }
    void UnlockQueue()
    {
        pthread_mutex_unlock(&_mutex);
    }
    void ThreadSleep() // 谁调用谁休眠
    {
        pthread_cond_wait(&_cond, &_mutex);
    }
    void ThreadWakeUp() // 唤醒一个线程
    {
        pthread_cond_signal(&_cond);
    }
    void ThreadWakeUpAll() // 唤醒全部线程
    {
        pthread_cond_broadcast(&_cond);
    }
    void HandlerTask(std::string name) // 第一个参数是this指针
    {
        while (true) // 一直处理任务,直到线程池退出且任务队列为空
        {
            LockQueue(); // 任务队列是临界资源,需要保护

            // 任务队列为空,且线程池还在运行,则线程休眠
            while (_task_queue.empty() && _isrunning)
            {
                _waitnum++;
                ThreadSleep();
                _waitnum--;
            }

            // 任务队列为空,且线程池不运行了,退出
            if (_task_queue.empty() && !_isrunning)
            {
                UnlockQueue(); // 解锁
                break;         // 退出
            }

            // 还有任务没处理,则处理任务
            T t = _task_queue.front(); // 取出任务
            _task_queue.pop();
            UnlockQueue(); // 线程已经取出任务,任务已为线程私有,且任务可能比较耗时,解锁

            LOG(DEBUG, "%s get a task", name.c_str());

            t();

            LOG(DEBUG, "%s handler a task,result is: %s", name.c_str(), t.ResultToString().c_str());
        }
    }

    // 单例模式下构造函数是私有的
    ThreadPool(int num = gdefaultthreadnum)
        : _threadnum(num), _waitnum(0), _isrunning(false)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
        LOG(INFO, "ThreadPool Construct");
    }
    // 只是传了线程需要的参数,线程还没有创建出来
    void InitThreadPool()
    {
        for (int i = 0; i < _threadnum; i++)
        {
            std::string name = "thread-" + std::to_string(i + 1);

            // 由于 HandlerTask 的第一个参数是 this 指针,第二个参数是 string 类型
            // 而 Thread.hpp 中 Thread 类的构造函数要求传的函数的参数只能有 string 类型
            // 可以用 bind 函数对 HandlerTask 的第一个参数进行绑定(绑了 this 指针)
            // (_1 就是绑定第一个参数,_2 就是绑定第二个参数)
            // 绑定之后,使用 HandlerTask 就只需要传 string 类型的参数
            // 相当于函数的参数从( this,string )变成了( string )
            _threads.emplace_back(std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1), name);
            LOG(INFO, "init thread %s done", name.c_str());
        }
        _isrunning = true;
    }

    // 创建线程
    void Start()
    {
        for (auto &thread : _threads)
        {
            thread.Start();
        }
    }

    // 禁用赋值和拷贝
    ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;
    ThreadPool(const ThreadPool<T> &) = delete;

public:
    static ThreadPool<T> *GetInstance()
    {
        // 如果是第一个调用,则会创建单例
        if (_instance == nullptr)
        {
            //不在if外面加锁,是因为如果是第二次调用的话,不需要创建单例,不需要保护资源
            //只需要获取单例,所以只需要在第一次调用的时候加锁,既保证线程安全又降低加锁成本
            LockGuard lockguard(&_lock);

            //第二次判断,防止在阻塞等待锁的过程中已经由别的线程创建单例了
            //但由于本线程已经通过if判断了,所以再创建了一次
            if (_instance == nullptr)
            {
                _instance = new ThreadPool<T>();
                _instance->InitThreadPool();
                _instance->Start();

                LOG(DEBUG, "创建线程池单例成功");
            }
        }
        // 不是第一次调用,则直接获取单例,不需要重新创建
        else
        {
            LOG(DEBUG, "获取线程池单例成功");
        }
        return _instance;
    }
    void Stop()
    {
        LockQueue(); // 状态值也是临界资源,需要保护

        _isrunning = false;

        // 线程池不运行了,需要唤醒所以在条件变量下等待的线程
        // 否则线程会一直阻塞等待条件变量,无法被 join
        ThreadWakeUpAll();

        UnlockQueue();
    }

    void Wait()
    {
        for (auto &thread : _threads)
        {
            thread.Join();
            LOG(INFO, "%s is quit...", thread.name().c_str());
        }
    }

    bool Enqueue(const T &t)
    {
        bool ret = false;
        LockQueue();

        // 线程池在运行中,才可以放入任务
        if (_isrunning)
        {
            _task_queue.push(t);

            // 有线程在等任务,唤醒线程
            if (_waitnum > 0)
                ThreadWakeUp();

            LOG(DEBUG, "enqueue task success");
            ret = true; // 任务插入成功
        }

        UnlockQueue();

        return ret;
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }

private:
    int _threadnum; // 线程的个数

    std::vector<Thread> _threads; // 管理线程
    std::queue<T> _task_queue;    // 任务队列

    pthread_mutex_t _mutex; // 互斥锁
    pthread_cond_t _cond;   // 信号量

    bool _isrunning; // 线程池的启动状态
    int _waitnum;    // 线程等待的个数

    // 添加单例模式
    static ThreadPool<T> *_instance;
    static pthread_mutex_t _lock;
};

// 初始化
template <typename T>
ThreadPool<T> *ThreadPool<T>::_instance = nullptr;

template <typename T>
pthread_mutex_t ThreadPool<T>::_lock = PTHREAD_MUTEX_INITIALIZER;


http://www.kler.cn/a/460223.html

相关文章:

  • 【Matlab算法】基于改进人工势场法的移动机器人路径规划研究(附MATLAB完整代码)
  • 出现 Error during query execution: StatementCallback; bad SQL grammar 解决方法
  • HTML 字符编码
  • 低代码开发:开启企业数智化转型“快捷键”
  • java接口下载zip,不生成中间文件,返回前端文件流
  • C++ 设计模式:模板方法(Template Method)
  • 本地小主机安装HomeAssistant开源智能家居平台打造个人AI管家
  • 国产数据库-崖山使用介绍
  • 【视觉SLAM:八、后端Ⅰ】
  • C语言插入排序及其优化
  • 微服务のGeteWay
  • iPhone修复技巧|iPhone异常问题的常用修复方法
  • Excel文件恢复教程:快速找回丢失数据!
  • 位运算与操作符应用
  • Element plus 的 upload 组件实现自定义上传
  • 为什么要用ZGC
  • rocketmq5源码系列--(二)--生产者发送消息
  • Java基础(五): 案例练习;
  • 2024年度AI与科技突破:量子计算引领创新浪潮
  • 实战设计模式之策略模式
  • 操作系统(26)数据一致性控制
  • 计算机网络•自顶向下方法:网络层介绍、路由器的组成
  • 网工日记:FTP两种工作模式的区别
  • dockerfile 安装 Python 依赖,修改为国内的镜像源
  • [react]小技巧, ts如何声明点击事件的类型
  • 快速了解开源日志框架log4net:灵活记录应用程序日志信息的利器