欢迎光临
我们一直在努力

【Linux】多线程之单例模式(七)

文章目录

  • 🚩单例模式
    • 🚩懒汉模式和饿汉模式
    • 饿汉模式实现单例模式
    • 懒汉模式实现单例模式
    • 🚩安全懒汉单例
    • 改造线程池
  • STL、智能指针与线程安全
  • 自旋锁
  • 读者写者问题

🚩单例模式

什么是单例模式? 单例模式是一种经典的设计模式, 什么是设计模式? 大佬们把经典场景总结出的解决方案叫设计模式

单例模式的特点,类只有一个对象

🚩懒汉模式和饿汉模式

举个例子: 吃完饭,我们立马刷碗,下次吃饭我们就不用刷碗了,这就是饿汉模式 吃完饭,我们不刷碗,等到下次吃饭我们再刷碗,这是懒汉模式

🚩懒汉模式可以优化服务器启动速度,为什么? 因为懒汉模式将申请内存和使用内存分开了,等到使用的时候再分配内存,核心功能:延时加载

且看下面代码

//懒汉模式,需要的时候再申请
class Init
{
public:
Init()
{
cout<<"Init success"<<endl;
}
~Init()
{}
};

int main()
{
sleep(3);
Init I;
sleep(3);
return 0;
}

终端中睡了3秒才打印Init success,正常现象 但如果

//饿汉模式,先申请
class Init
{
public:
Init()
{
cout<<"Init success"<<endl;
}
~Init()
{}
};
Init I;

int main()
{
sleep(3);
sleep(3);
return 0;
}

把对象设为全局变量,立马就打印Init success

因为线程启动时,会提前分配好资源,全局变量已经初始化到全局变量区了

Init I;
Init I1;
Init I2;
Init I3;
Init I4;
Init I5;

如果我们全局变量定义很多,就会影响线程启动速度了

饿汉模式实现单例模式

template<class T>
class Singleton
{
static T data;
private:
Singleton()
{}
~Singleton()
Singleton(const Singlenton&);
Singleton& operator=(const Singleton&);
public:
T *GetInstance()
{
return &data;
}
};
template<class T>
T Singleton<T>::data;

T是静态变量,只会初始化一次 通过使用包装类SIngleton<T>::date,则一个进程只有一个T对象,

懒汉模式实现单例模式

template<class T>
class Singleton{
static T* inst;
private:
Singleton()
{}
~Singleton()
{}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
public:
static T *GetInst()
{
if(inst==nullptr)
{
inst =new T(); //延时加载
}
return inst;
}
};
template<class T>
T* Singleton<T>::inst=nullptr;

需要时再分配空间, 但有问题啊,如果inst为空,多线程共同进入函数,岂不是new了多次 所以必须加锁

🚩安全懒汉单例

template<class T>
class Singleton{
static T* inst;
static pthread_mutex_t _lock;
private:
Singleton()
{}
~Singleton()
{}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);

public:
static T *GetInst()
{
if (inst == nullptr)
{
pthread_mutex_lock(&_lock);
if (inst == nullptr)
{
inst = new T();
}
pthread_mutex_unlock(&_lock)
}
return inst;
}

};
template<class T>
T* Singleton<T>::inst=nullptr;

template<class T>
pthread_mutex_t Singleton<T>::_lock=PTHREAD_MUTEX_INITIALIZER;

静态加锁,inst为什么双重判空? 因为加锁只要一次,inst永远不为空了,不必再申请锁判断了

改造线程池

上篇文章我们的线程池就可以改造成单例模式,因为我们服务器一般只有一个对象

#pragma once
#include <iostream>
#include <vector>
#include <pthread.h>
#include <queue>
#include <unistd.h>

using namespace std;
class ThreadInto
{
public:
pthread_t tid;
string threadname;
};

static int defaultnum=5;
template<class T>
class pthread_pool
{

private:
void Lock()
{
pthread_mutex_lock(&_mutex);
}
void Unlock()
{
pthread_mutex_unlock(&_mutex);
}
void Wakeup()
{
pthread_cond_signal(&_cond);
}
void ThreadSleep()
{
pthread_cond_wait(&_cond,&_mutex);
}
string Getname(pthread_t tid)
{
for(const auto &tq:_threads)
{
if(tq.tid==tid)
{
return tq.threadname;
}
}
return "None";
}

pthread_pool<T>(int num=defaultnum)
:_threads(num)
{
pthread_mutex_init(&_mutex,nullptr);
pthread_cond_init(&_cond,nullptr);
}
~pthread_pool<T>()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
pthread_pool(const pthread_pool<T>&)=delete;
const pthread_pool<T> &operator=(const pthread_pool<T>&)=delete;

public:
static void* Taskhandler(void* args)
{
pthread_pool<T>* tq=static_cast<pthread_pool<T>*>(args);
string name=tq->Getname(pthread_self());
while(1)
{
tq->Lock();
while(tq->Queue_Empty())
{
tq->ThreadSleep();
}
T t=tq->Pop();
tq->Unlock();
t();
cout<<name<<": run success"<<t.Getresult()<<endl;
sleep(1);
}
}
bool Queue_Empty()
{
return _q.empty();
}

void start()
{
for(int i=0;i<_threads.size();i++)
{
_threads[i].threadname="thread-"+to_string(i+1);
pthread_create(&_threads[i].tid,nullptr,Taskhandler,this);
}
}

void push(const T &t)
{
Lock();
_q.push(t);
Wakeup();
Unlock();
}
T Pop()
{
T t=_q.front();
_q.pop();
return t;
}
static pthread_pool<T>* GetInstance(int num=defaultnum)
{
if(_pp==nullptr)
{
pthread_mutex_lock(&_lock);
if(_pp==nullptr)
{
_pp=new pthread_pool<T>(num);
}
pthread_mutex_unlock(&_lock);
return _pp;
}
}

private:
vector<ThreadInto> _threads;
queue<T> _q;

pthread_mutex_t _mutex;
pthread_cond_t _cond;

static pthread_pool<T>* _pp;
static pthread_mutex_t _lock;
};
template<class T>
pthread_pool<T> *pthread_pool<T>::_pp=nullptr;

template<class T>
pthread_mutex_t pthread_pool<T>::_lock=PTHREAD_MUTEX_INITIALIZER;

main函数中这样使用

#include <iostream>
#include "task.hpp"
#include "pthread-pool.hpp"
#include <ctime>
using namespace std;
int main()
{
pthread_pool<Task> *pool=pthread_pool<Task>::GetInstance(3);
pool->start();
srand(time(nullptr));
while(1)
{
int date1=rand()%10;
int date2=rand()%5;
char op=len[rand()%len.size()];
Task t(date1,date2,op);
pool->push(t);
cout<<"Main thread make task:"<<t.Gettask()<<endl;
sleep(1);
}
return 0;
}

STL、智能指针与线程安全

STL线程安全吗 不安全,STL目的把性能挖掘到极致,加锁解锁只会影响性能

智能指针 unique_ptr只在当前代码块生效,所以不涉及线程安全问题 shared_ptr,多个线程会共用引用计数变量,所以会涉及线程安全问题,但标准库考虑了这个问题,设计原子操作引用计数,故安全 智能指针都是线程安全的

自旋锁

加锁——临界区——解锁 对于临界区执行时间长的,我们没抢到锁可以挂起等待 但对于临界区执行时间很短,我们没必要等待,可以不断自旋,不断申请锁

在这里插入图片描述 trylock申请锁,如果锁被占用,直接返回EBUSY,成功返回0

实现:

while(pthread_mutex_trylock(&_lock)!=0); 不断申请锁,失败重新申请锁

读者写者问题

类似于生产消费者模型, 3种关系,

  • 写写(互斥)
  • 写读(互斥同步)
  • 读读(共享)

2个对象 读者写者 1个数据交换场所

在这里插入图片描述 接口类似互斥锁

正常情况下,读者肯定是比写着多的,所以读者锁竞争更强,读者优先,容易导致写者饥饿问题,这是正常现象,

伪代码理解:

int reader_count=0;
mutex_t _rlock,_wlock;

//读者
lock(&_rlock);
reader_count++;
if(reader_count==1)lock(&_wlock)
unlock(&_rlock);

//读取

lock(&_rock);
reader_count—;
if(reader_count==0)lock(&_wlock);
unlock(&_rlock);

//写者
lock(&_wlock);
//写
unlock(&_wlock);

也可以定义write_count,写者优先,

int reader_count = 0;
int writer_count = 0; // 正在写或等待写的写者数量
mutex_t m; // 保护所有共享变量
cond_t reader_cv, writer_cv;

// 读者
lock(&m);
//只要有写者在写,读者就等待
while (writer_count > 0) {
wait(&reader_cv, &m);
}
reader_count++;
unlock(&m);

// 读取

lock(&m);
reader_count—;
if (reader_count == 0) {
signal(&writer_cv); // 唤醒等待的写者
}
unlock(&m);

赞(0)
未经允许不得转载:171主机测评 » 【Linux】多线程之单例模式(七)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址