Linux -- 单例模式
目录
概念
主要特点
饿汉模式和懒汉模式
饿汉模式
懒汉模式
应用场景
单例模式下的线程池
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;
// 线程池不运行了,需要唤醒所以在条件变量下等待的线程
// 否则线程会一直阻塞等待条件变量,无法被 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;