单例模式

单例模式

单例模式就是一种“经典的,常用的,常考的” 设计模式

什么是设计模式

大佬对于一些常见的场景,给定了一个特定对应的解决方案,这个就是设计模式,这个是可以被推广使用的

单例模式的特定

某些类,只应该需要又一个对象(实例),就称为单例

例如:一个男人只能取一个老婆

类 对象

threadpool tp

定义一个对象:开辟空间+给空间写入初始值(本质上就是将对象加载到内存里面)

只让该对象在内存中存在一份,加载一次

一般而言,我们的对象被设计称单例模式

  1. 语意上,我们只需要一个
  2. 该对象内部存在有大量的空间,保存了大量的数据,如果允许该对象存在多份的话,或者允许发生各自拷贝,内存中存在冗余数据

这一般情况下,我们都可以设计成为单例模式

那么什么时候创建呢?

1. 饿汉模式

吃完饭,立刻洗碗,因为这样,吃下一顿的时候立刻就能把饭拿到手里

饿汉模式实现单例模式

1
2
3
4
5
6
7
8
9
10
11
12

template<class T>
class singleton{
static T data;
public:
static T* getinstance()
{
return &data;
}

}

只通过singleton这个包装类来使用T 对象,则一个进程中只有一个T 对象的实例

像这种静态成员,只要创建了一个对象这个静态成员立刻就开辟了,这就叫做饿汉模式

2. 懒汉模式

吃完饭,先把碗放下来,等到下一次要吃饭的时候再去洗这个碗

(延迟加载),在用的时候再加载,

写时拷贝(在用的时候,再拷贝),申请空间(不需要的时候就不用再做),优化服务器的启动速度

懒汉模式创建单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<class T>

class singleton
{
static T* inst;//创建了一个类型指针,这个静态成员变量要在外面初始化为nullptr
public:
static T*getinstance()//如果我们创建了一个类,当时很长时间都不调用这个,就不会创建
{
if(inst==NULL)
{
inst=new T();//发现为空,外面就创建,否则就直接返回,只创建一个对象
}
return inst;//以后每次使用就直接返回他的地址
}
}

智能指针是线程安全的

单例模式实现线程池

task

task.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#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;
}

单例模式
http://example.com/2022/06/03/单例模式/
作者
Zevin
发布于
2022年6月3日
许可协议