什么是单例模式
单例模式是一种
"
经典的
,
常用的
,
常考的
"
设计模式
什么是设计模式
IT
行业这么火
,
涌入的人很多
.
俗话说林子大了啥鸟都有
.
大佬和菜鸡们两极分化的越来越严重
.
为了让菜鸡们不太拖大 佬的后腿,
于是大佬们针对一些经典的常见的场景
,
给定了一些对应的解决方案
,
这个就是
设计模式。
单例模式的特点
某些类
,
只应该具有一个对象
(
实例
),
就称之为单例
.
例如一个男人只能有一个媳妇
.
在很多服务器开发场景中
,
经常需要让服务器加载很多的数据
(
上百
G)
到内存中
.
此时往往要用一个单例的类来管理这 些数据.
饿汉实现方式和懒汉实现方式
[
洗完的例子
]
吃完饭
,
立刻洗碗
,
这种就是饿汉方式
.
因为下一顿吃的时候可以立刻拿着碗就能吃饭
.
吃完饭
,
先把碗放下
,
然后下一顿饭用到这个碗了再洗碗
,
就是懒汉方式
.
懒汉方式最核心的思想是
"
延时加载
".
从而能够优化服务器的启动速度
.
饿汉方式实现单例模式
template <typename T> class Singleton { static T data; public: static T* GetInstance() { return &data; } };
只要通过
Singleton
这个包装类来使用
T
对象
,
则一个进程中只有一个
T
对象的实例
懒汉方式实现单例模式
template <typename T> class Singleton { static T* inst; public: static T* GetInstance() { if (inst == NULL) { inst = new T(); } return inst; } };
存在一个严重的问题
,
线程不安全
.
第一次调用
GetInstance
的时候
,
如果两个线程同时调用
,
可能会创建出两份
T
对象的实例
.
但是后续再次调用
,
就没有问题了
.
懒汉方式实现单例模式
(
线程安全版本
)
#include <iostream>
#include <queue>
#include <pthread.h>
namespace ns_threadpool
{
template <class T>
class PthreadPool
{
public:
PthreadPool(int num = 5)
: _num(num)
{
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_cond, nullptr);
}
static PthreadPool<T> * GetInstace()
{
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
if (_ins == nullptr) //双判断提高效率
{
pthread_mutex_lock(&lock);
if (_ins == nullptr)
{
_ins = new PthreadPool<T>();
_ins->InitPthreadPool();
std::cout << "首次加载对象" << std::endl;
}
pthread_mutex_unlock(&lock);
}
return _ins;
}
static void *Rountine(void *args) //因为在类中所以必须要是静态成员,因为有this指针
{
pthread_detach(pthread_self());
PthreadPool<T> *q = (PthreadPool<T> *)args;
for (;;)
{
q->Lock();
while (q->IsEmpty())
{
q->Wait();
}
T t;
q->PopTask(&t);
q->Unlock();
t();
}
}
void InitPthreadPool()
{
pthread_t tid;
for (size_t i = 0; i < _num; ++i)
{
pthread_create(&tid, nullptr, Rountine, (void *)this);
}
}
void PushTask(const T &in)
{
Lock();
_task_queue.push(in);
Unlock();
WackUp();
}
~PthreadPool()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
private:
void Lock()
{
pthread_mutex_lock(&_mutex);
}
void Unlock()
{
pthread_mutex_unlock(&_mutex);
}
void PopTask(T *out)
{
*out = _task_queue.front();
_task_queue.pop();
}
bool IsEmpty()
{
return _task_queue.empty();
}
void Wait()
{
pthread_cond_wait(&_cond, &_mutex);
}
void WackUp()
{
pthread_cond_signal(&_cond);
}
private:
std::queue<T> _task_queue;
int _num;
pthread_mutex_t _mutex;
pthread_cond_t _cond;
static PthreadPool<T>* _ins;
};
template <class T>
PthreadPool<T>* PthreadPool<T>::_ins = nullptr;
}
注意事项
:
1.
加锁解锁的位置
2.
双重
if
判定
,
避免不必要的锁竞争
其他常见的各种锁
悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前, 会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS
操作。
CAS
操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不
等则失败,失败则重试,一般是一个自旋的过程,即不断重试。
自旋锁,公平锁,非公平锁?
悲观锁前期我们写的代码全是悲观锁举个简单的例子:
小李来找小张去学习,但是小张告诉小李他现在给女朋友打电话,要很久,那么小李说那好吧,我先去网吧玩一会,等你好了我在过来。等过了一个1小时,小张告诉小李我好了,小李就从网吧过来和小李一起学习。一种情况可以理解为悲观锁。
小李来找小张去学习,小张说等我1分钟我马上下来,过了1分钟小张还没下来,小李就打电话问还要多久,小李一直打电话问。这中情况可以理解为自旋锁。
可以看出悲观锁是有成本的,小李需要在网络和小张来回走,但是自旋锁也有成本会一直询问,小张是否下来了。
初始化
#include <pthread.h>
int pthread_spin_destroy(pthread_spinlock_t *lock);
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
销毁
#include <pthread.h>
int pthread_spin_destroy(pthread_spinlock_t *lock);
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
加锁
#include <pthread.h>
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
解锁
#include <pthread.h>
int pthread_spin_unlock(pthread_spinlock_t *lock);
可见跟我们平时用的锁机会没什么差别。我就用自旋锁来实现一个抢票,效果与之前一样
#include<iostream>
#include<unistd.h>
#include<pthread.h>
#define NUM 5
pthread_spinlock_t spin;
int tickets = 1000;
void *run(void *args)
{
while(true)
{
pthread_spin_lock(&spin);
std::cout<<pthread_self()<<"我枪到的票是:"<<tickets<<std::endl;
tickets--;
pthread_spin_unlock(&spin);
sleep(1);
}
}
int main()
{
pthread_t tid[NUM];
pthread_spin_init(&spin,PTHREAD_PROCESS_SHARED);
for(size_t i = 0; i< NUM; ++i)
{
pthread_create(tid+i,nullptr,run,nullptr);
}
pthread_spin_destroy(&spin);
for(size_t i = 0; i<NUM; ++i)
{
pthread_join(tid[i],nullptr);
}
}
乐观锁:我将会mysql中详细讲解。
读者写者问题
读写锁
在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的 机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地 降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。

读写锁接口
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);
/*
pref 共有 3 种选择
PTHREAD_RWLOCK_PREFER_READER_NP (默认设置) 读者优先,可能会导致写者饥饿情况
PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为和 PTHREAD_RWLOCK_PREFER_READER_NP 一致
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁 */
初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
加锁和解锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);