目录
概念
主要特点
饿汉模式和懒汉模式
饿汉模式
懒汉模式
应用场景
单例模式下的线程池
GetInstance 函数:
单例模式禁止赋值和拷贝:
完整代码:
概念
单例模式是软件工程中的一种设计模式,属于创建型模式。它确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。
主要特点
- 唯一实例:整个应用程序只能存在该类的一个实例。
- 私有构造函数:防止其他对象使用new关键字直接实例化。
- 静态工厂方法:提供一个静态方法作为创建实例的全局访问点。
- 延迟初始化:有些实现方式会直到第一次使用时才创建实例,以节省资源。
单例模式的类禁止赋值或拷贝,否则破坏单例原则!!
饿汉模式和懒汉模式
饿汉模式和懒汉模式是实现单例模式的两种常见策略,它们主要区别在于何时创建单例实例。
饿汉模式
饿汉模式是在类加载时就立即创建单例对象。这种方式简单且线程安全,因为对象在类加载时就已经被创建,所以在多线程环境下不会出现两个线程同时创建对象的情况。缺点是如果这个对象在整个应用程序运行期间都不被使用,那么将造成资源浪费。
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;// 线程池不运行了,需要唤醒所以在条件变量下等待的线程// 否则线程会一直阻塞等待条件变量,无法被 joinThreadWakeUpAll();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;