什么是单例模式
单例模式是一种 "经典的, 常用的, 常考的" 设计模式.
什么是设计模式
IT行业这么火, 涌入的人很多. 俗话说林子大了啥鸟都有. 大佬和菜鸡们两极分化的越来越严重. 为了让菜鸡们不太拖大佬的后腿, 于是大佬们针对一些经典的常见的场景, 给定了一些对应的解决方案, 这个就是设计模式
单例模式的特点
某些类, 只应该具有一个 对象(实例), 就称之为单例.
例如一个男人只能有一个媳妇.
在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据.
饿汉实现方式和懒汉实现方式
洗完的例子
吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭.
吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式.
懒汉方式最核心的思想是 "延时加载". 从而能够优化服务器的启动速度.
饿汉方式实现单例模式
cpp
template <typename T>
class Singleton {
static T data;
public:
static T* GetInstance() {
return &data;
}
};
只要通过 Singleton 这个包装类来使用 T 对象, 则一个进程中只有一个 T 对象的实例
懒汉方式实现单例模式
cpp
template <typename T>
class Singleton {
static T* inst;
public:
static T* GetInstance() {
if (inst == NULL) {
inst = new T();
}
return inst;
}
};
存在一个严重的问题, 线程不安全.
第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例.
但是后续再次调用, 就没有问题了.
下面把这一篇文章的线程池改为懒汉模式的单例,改动了构造和析构函数,防止构造出第二个对象,多了一个静态的类的指针成员,来获取这个类
Threadpool.hpp
cpp
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include "task.hpp"
using namespace std;
struct ThreadInfo
{
pthread_t tid;
string name;
};
template <class T>
class ThreadPool
{
public:
void Lock()
{
pthread_mutex_lock(&mutex_);
}
void Unlock()
{
pthread_mutex_unlock(&mutex_);
}
void Threadsleep()
{
pthread_cond_wait(&cond_, &mutex_);
}
void Wakeup()
{
pthread_cond_signal(&cond_);
}
bool IsQueueEmpty()
{
return tasks_.empty();
}
string GetThreadName(pthread_t tid)
{
for (const auto &ti : threads_)
{
if (ti.tid == tid)
return ti.name;
}
return "None";
}
public:
static void *HandlerTask(void *args) // 必须定义为静态函数,这样才不会传this指针过来,才不会导致函数不匹配问题
{
ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
string name = tp->GetThreadName(pthread_self());
while (true)
{
tp->Lock();
while (tp->IsQueueEmpty())
{
tp->Threadsleep();
}
// 消费任务
T t = tp->Pop();
tp->Unlock();
// 处理任务
t();
cout << name << " run, " << "result: " << t.GetResult() << endl;
}
return nullptr;
}
void Start()
{
int size = threads_.size();
for (int i = 0; i < size; i++)
{
threads_[i].name = "thread-" + to_string(i + 1);
pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this); // 传入this指针,使静态函数可以访问类内成员和函数
}
}
T Pop()
{
T out = tasks_.front();
tasks_.pop();
return out;
}
void Push(const T &in)
{
// 需要加锁
Lock();
tasks_.push(in);
Wakeup();
Unlock();
}
static ThreadPool<T> *GetInstance()
{
if (tp_ == nullptr)//???假如已经是空指针了,就不用申请锁进去什么事也没干又释放锁,降低效率
{
pthread_mutex_lock(&lock_);//tp_是临界资源需要锁的保护
if (tp_ == nullptr)
{
std::cout << "log: singleton create done first!" << std::endl;
tp_ = new ThreadPool<T>;
}
pthread_mutex_unlock(&lock_);
}
return tp_;
}
private:
// 把构造析构私有化,和没有必要的拷贝构造和赋值重载delete,防止定义出第二个对象
ThreadPool(int num = 5) : threads_(num)
{
pthread_mutex_init(&mutex_, nullptr);
pthread_cond_init(&cond_, nullptr);
}
~ThreadPool()
{
pthread_mutex_destroy(&mutex_);
pthread_cond_destroy(&cond_);
}
ThreadPool(const ThreadPool<T> &tp) = delete;
const ThreadPool<T> &operator=(const ThreadPool<T> &tp) = delete;
private:
vector<ThreadInfo> threads_; // 用数组管理创建出来的线程
queue<T> tasks_; // 用队列来管理任务
pthread_mutex_t mutex_;
pthread_cond_t cond_;
static ThreadPool<T> *tp_; // 静态成员在类外定义
static pthread_mutex_t lock_;
};
template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;
template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;
cpp
#include<iostream>
#include<unistd.h>
#include<time.h>
#include"threadpool.hpp"
using namespace std;
int main()
{
ThreadPool<Task>::GetInstance()->Start();
while(true)
{
//1. 构建任务
int x = rand() % 10 + 1;
usleep(10);
int y = rand() % 5;
char op = opers[rand()%opers.size()];
Task t(x, y, op);
ThreadPool<Task>::GetInstance()->Push(t);
//2. 交给线程池处理
std::cout << "main thread make task: " << t.GetTask() << std::endl;
sleep(1);
}
}
Task.hpp文件和上面那篇文章的是一样的