单例模式
单例模式就是一种“经典的,常用的,常考的” 设计模式
什么是设计模式
大佬对于一些常见的场景,给定了一个特定对应的解决方案,这个就是设计模式,这个是可以被推广使用的
单例模式的特定
某些类,只应该需要又一个对象(实例),就称为单例
例如:一个男人只能取一个老婆
类 对象
threadpool tp
定义一个对象:开辟空间+给空间写入初始值(本质上就是将对象加载到内存里面)
只让该对象在内存中存在一份,加载一次
一般而言,我们的对象被设计称单例模式
- 语意上,我们只需要一个
- 该对象内部存在有大量的空间,保存了大量的数据,如果允许该对象存在多份的话,或者允许发生各自拷贝,内存中存在冗余数据
这一般情况下,我们都可以设计成为单例模式
那么什么时候创建呢?
1. 饿汉模式
吃完饭,立刻洗碗,因为这样,吃下一顿的时候立刻就能把饭拿到手里
饿汉模式实现单例模式
template<class T>
class singleton{
static T data;
public:
static T* getinstance()
{
return &data;
}
}
只通过singleton这个包装类来使用T 对象,则一个进程中只有一个T 对象的实例
像这种静态成员,只要创建了一个对象这个静态成员立刻就开辟了,这就叫做饿汉模式
2. 懒汉模式
吃完饭,先把碗放下来,等到下一次要吃饭的时候再去洗这个碗
(延迟加载),在用的时候再加载,
写时拷贝(在用的时候,再拷贝),申请空间(不需要的时候就不用再做),优化服务器的启动速度
懒汉模式创建单例模式
template<class T>
class singleton
{
static T* inst;//创建了一个类型指针,这个静态成员变量要在外面初始化为nullptr
public:
static T*getinstance()//如果我们创建了一个类,当时很长时间都不调用这个,就不会创建
{
if(inst==NULL)
{
inst=new T();//发现为空,外面就创建,否则就直接返回,只创建一个对象
}
return inst;//以后每次使用就直接返回他的地址
}
}
智能指针是线程安全的
单例模式实现线程池
task
task.hpp
#pragma once
#include <iostream>
#include <pthread.h>
namespace ns_task
{
class Task
{
private:
int _x;
int _y;
char _op; //表示+-*/%
public:
Task() //无参构造,为了拿任务,不需要参数列表
{
}
//进行函数重载
Task(int x, int y, char op) //有参构造,制造任务
: _x(x), _y(y), _op(op)
{
}
~Task()
{
}
int Run()
{
int res = 0;
switch (_op)
{
case '+':
res = _x + _y;
break;
case '-':
res = _x - _y;
break;
case '*':
res = _x * _y;
break;
case '/':
res = _x / _y;
break;
case '%':
res = _x % _y;
break;
default:
std::cout << "bug?" << std::endl;
break;
}
std::cout << "当前任务正在被:" << pthread_self() << "处理:" << _x << _op << _y << "=" << res << std::endl;
return res;
}
Task operator=(Task &s)
{
if (this != &s)
{
_x = s._x;
_y = s._y;
_op = s._op;
}
return *this;
}
int operator()()//重载一个函数
{
return Run();
}
};
}
threadpool
threadpool.hpp
#pragma once
#include <iostream>
#include <string>
#include <queue>
#include <unistd.h>
#include <pthread.h>
// #cludine"Task.hpp"
// using namespace ns_task;
namespace ns_threadpool
{
const int g_num = 5;
template <class T>
class ThreadPool //线程池
{
private:
int num_; //一个线程池里面有多少个任务
std::queue<T> task_queue_; //任务队列,临界资源
pthread_mutex_t mtx_;
pthread_cond_t cond_;
static ThreadPool<T> *ins; //静态成员在所有的对象里面只有一个静态成员,必须要通过静态变量来获取对象
//保存内存的可见性
private:
//单例的话,就不能让构造函数暴露在外面,否则,只有有构造函数,就能初始化
//构造函数必须得实现,当时必须得私有
ThreadPool(int num = g_num) : num_(num)
{
pthread_mutex_init(&mtx_, nullptr);
pthread_cond_init(&cond_, nullptr);
}
ThreadPool(const ThreadPool &tp) = delete;
// c++11的新特性
//静止编译器生成拷贝构造,
//=delete就是禁止调用这个函数,在私有里面
ThreadPool operator=(const ThreadPool &tp) = delete;
//把赋值运算符也禁止掉,这也就可以避免创建多个对象
public:
static ThreadPool<T> *GetInstance() //这个必须是使用静态的,非静态函数都是有对象的,静态函数才是没对象的
{
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; //使用静态的初始化
if (ins == nullptr)//双判定,减少锁的争用,提高单例获取的效率,
//假如说有的线程进来发现不为空,就可以直接走了,如果同时为nullptr的化,那么再把他们放进来争抢锁资源、
{
pthread_mutex_lock(&lock); //争抢锁的过程就是一个串行化的过程,成本很高
//当前的单例对象还没有被创建
if (ins == nullptr)
//假如是在多线程的情况下,那么多个线程执行的时候,都是nullptr,都创建了对象,那么就出现了线程安全
{
//就创建它
ins = new ThreadPool<T>(); //创建一个,使用构造函数
//创建出来了一个单例之后,就直接给他初始化一个池就行了
ins->InitThreadPool();
std::cout << "首次加载对象" << std::endl;
}
pthread_mutex_unlock(&lock);
}
return ins;
}
~ThreadPool()
{
pthread_mutex_destroy(&mtx_);
pthread_cond_destroy(&cond_);
}
//在类中,要让
static void *Rountine(void *args)
//也不能访问类里面非static成员
{
pthread_detach(pthread_self()); //实现线程分离就不要再去join等待了
ThreadPool<T> *tp = (ThreadPool<T> *)args;
while (true)
{
//从任务队列里面去拿一个任务
//执行任务,要先把这个任务队列锁主
//每个线程他跟放任务的线程一样,都是竞争式的去拿一个任务
tp->Lock();
//先检测任务队列是否有一个任务
while (tp->IsEmpty())
{
//检测到任务队列为空
//此时线程就挂起等待
tp->Wait();
}
//该任务队列里面一定有任务了
T t;
tp->PopTask(&t);
//任务就拿到了
tp->UnLock();
t.Run(); //可能有多个线程在处理任务,
sleep(1);
}
}
void InitThreadPool()
{
//初始化一批线程,
//这样就不要每次用都要去开辟线程了
pthread_t tid; //一次创建一批线程
for (int i = 0; i < num_; i++)
{
pthread_create(&tid, nullptr, Rountine, (void *)this);
//在类中不能执行线程的方法,因为他都有隐藏的this指针
//所以我们需要使用静态的函数,就没有了this指针
}
}
void PopTask(T *out)
{
*out = task_queue_.front();
task_queue_.pop();
}
void Wait()
{
pthread_cond_wait(&cond_, &mtx_);
}
bool IsEmpty()
{
return task_queue_.empty();
}
void Lock()
{
pthread_mutex_lock(&mtx_);
}
void UnLock()
{
pthread_mutex_unlock(&mtx_);
}
void Wakeup()
{
pthread_cond_signal(&cond_);
}
void PushTask(const T &in)
{
//塞任务,就相当于一个生产者,生产者之间要进行互斥访问
Lock();
task_queue_.push(in);
UnLock();
Wakeup();
}
//万一任务队列里面一个任务都没有的话,那么线程池里面的每一个线程就要处于休眠状态,挂起等待
};
template <class T>
//静态成员变量的初始化必须要在类外面初始化
ThreadPool<T> *ThreadPool<T>::ins = nullptr; //将threadpool里面的ins进行初始化,返回值是指针,给它初始化为空,说明没有被创建出来
}
main
main.cpp
#include "threadpool.hpp"
#include "Task.hpp"
#include <unistd.h>
#include <cstdlib>
#include <ctime>
using namespace ns_task;
using namespace ns_threadpool;
//线程池就是一个单例模式,只有一个线程池就够了
int main()
{
std::cout << "当前正在执行我的进程其他代码......" << std::endl;
std::cout << "当前正在执行我的进程其他代码......" << std::endl;
std::cout << "当前正在执行我的进程其他代码......" << std::endl;
std::cout << "当前正在执行我的进程其他代码......" << std::endl;
std::cout << "当前正在执行我的进程其他代码......" << std::endl;
sleep(5);
//前5秒是没有这个单例的
//我们希望是主线程不断的向线程池里面push任务,线程池里面竞争任务,处理这些任务
//外部可能存在一个或者多个线程向里面塞任务
srand((long long)time(nullptr));
while (true)
{
//以后就是从网络里面来
//主线程就是把任务放到线程池里面去
//有的时候访问网站,挂掉了,OS受不了了,杀掉
sleep(1);
Task t(rand() % 20 + 1, rand() % 10 + 1, "+-*/%"[rand() % 5]);
ThreadPool<Task>::GetInstance()->PushTask(t); //在这里获得单例
//即使是死循环入数据,也就只有一个对象,因为是静态的
//只会调用一次
//Getinstance
}
return 0;
}