目录
信号量 posix
#include <semaphore.h>int sem_init(sem_t *sem, int pshared, unsigned int value);参数:pshared:0 表示线程间共享,非零表示进程间共享value :信号量初始值
基于环形队列的生产消费模型
环形队列的生产消费模型,利用数组的下标通过取模运算,模拟环形队列,使用两个下标记录分别表示消费者,与生产者的消费与生产,类似双指针。当两个下标相遇即为 队列为满 或者空,我们不知道,因此我们还引用了信号量分别记录,生产者需要的空间大小,以及消费者需要的资源大小。
通过两个下标分别表示生产与消费,从而实现了避免互斥的情况,即不需要加锁。
RingQueue.hpp
//设计环形队列
static int defaltcapacity=10;
template<class T>
class RingQueue
{
private:
void P(sem_t &sem)
{
sem_wait(&sem);
}
void V(sem_t &sem)
{
sem_post(&sem);
}
public:
RingQueue(int capacity=defaltcapacity):_rq(capacity),_capacity(capacity),c_index(0),p_index(0)
{
sem_init(&p_sem_cap,0,_capacity);
sem_init(&c_sem_data,0,0);
}
int GetIndex()
{
return p_index;
}
void Push(const T& temp)
{
//对信号量进行PV操作
P(p_sem_cap); //空间减一
_rq[p_index]=temp;
V(c_sem_data); //资源加一
//之后位置后移,保持环状
p_index++;
p_index%=_capacity;
}
void Pop(T *out)
{
P(c_sem_data);
//用消费者下标表示消费
*out=_rq[c_index];
V(p_sem_cap);
c_index++;
c_index%=_capacity;
}
~RingQueue()
{
sem_destroy(&p_sem_cap);
sem_destroy(&c_sem_data);
}
private:
std::vector<T> _rq; //环形队列
//生产与消费 可以当作两个指针,这里就是下标,相遇可能是满,可能是空,这时取决于信号量
int _capacity; //容量
int c_index; //生产者下标
int p_index; //消费者下标
//使用了两个下标,从而不会产生互斥
sem_t p_sem_cap; //生产者空间信号量
sem_t c_sem_data; //消费者资源信号量
};
那如果是多生产多消费模型,生产者与生产者之间相互竞争下标,消费者与消费者之间相互竞争下标,因此生产的时候与消费的时候,需要加锁。
需要注意一点,信号量的申请与所得申请的顺序:
先申请信号量,再申请锁是较好的,先申请信号量同时也会申请锁,一申请锁成功,我们进入执行了,其次信号量数据不需要让锁保护着,他们两不相关。
增加以及修改的部分:
void Lock(pthread_mutex_t &mutex)
{
pthread_mutex_lock(&mutex);
}
void Unlock(pthread_mutex_t &mutex)
{
pthread_mutex_unlock(&mutex);
}
public:
RingQueue(int capacity=defaltcapacity):_rq(capacity),_capacity(capacity),c_index(0),p_index(0)
{
sem_init(&p_sem_cap,0,_capacity);
sem_init(&c_sem_data,0,0);
pthread_mutex_init(&pmutex,nullptr);
pthread_mutex_init(&cmutex,nullptr);
}
void Push(const T& temp)
{
{
//上锁
P(p_sem_cap); //空间减一
Lock(pmutex);
//对信号量进行PV操作
_rq[p_index]=temp;
//之后位置后移,保持环状
p_index++;
p_index%=_capacity;
Unlock(pmutex);
V(c_sem_data); //资源加一
}
}
void Pop(T *out)
{
{
//上锁
P(c_sem_data);
Lock(cmutex);
//用消费者下标表示消费
*out=_rq[c_index];
c_index++;
c_index%=_capacity;
Unlock(cmutex);
V(p_sem_cap);
}
}
~RingQueue()
{
sem_destroy(&p_sem_cap);
sem_destroy(&c_sem_data);
pthread_mutex_destroy(&pmutex);
pthread_mutex_destroy(&cmutex);
}
//多线程情况下我们用两把锁分别让生产者与消费者按顺序来
pthread_mutex_t pmutex;
pthread_mutex_t cmutex;
};
池化技术
本质:以空间换时间
把一些资源预先分配好,组织到对象池中,之后的业务使用资源从对象池中获取,使用完后放回到对象池中。
线程池
这里以一个简单的c++类内的原生线程为例:
struct ThreadInfo
{
ThreadInfo()
{}
ThreadInfo(pthread_t id,std::string Name):tid(id),name(Name)
{}
pthread_t tid;
std::string name;
};
static const int defaultnum =5;
template<class T>class ThreadPool
{
private:
void Lock()
{
pthread_mutex_lock(&_mutex);
}
void Unlock()
{
pthread_mutex_unlock(&_mutex);
}
void Wake()
{
pthread_cond_signal(&_cond);
}
void Wait()
{
pthread_cond_wait(&_cond,&_mutex);
}
std::string GetThreadName(pthread_t tid)
{
for (const auto &ti : threads)
{
if (ti.tid == tid)
return ti.name;
}
return "None";
}
public:
ThreadPool(int capacity=defaultnum):threads(capacity),_capacity(capacity)
{
pthread_cond_init(&_cond,nullptr);
pthread_mutex_init(&_mutex,nullptr);
}
static void *Handler(void *args)
{
ThreadPool<T> *p = static_cast<ThreadPool<T> *>(args);
std::string name = p->GetThreadName(pthread_self());
while(true)
{
p->Lock();
//没有任务,阻塞队列等待
while(p->_task.empty())
{
p->Wait();
}
T t = p->Pop();
p->Unlock();
t();
std::cout << name << " run, "<< "result: " << t.GetResult() << std::endl;
}
}
void start()
{
for(int i=0;i<threads.size();i++)
{
threads[i].name="thread_"+std::to_string(1+i);
pthread_create(&(threads[i].tid),nullptr,Handler,this);
}
}
void Push(const T& temp)
{
//生产任务
Lock();
_task.push(temp);
//有任务了。可以唤醒线程
Wake();
Unlock();
}
T Pop()
{
T t=_task.front();
_task.pop();
return t;
}
~ThreadPool()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
private:
std::vector<ThreadInfo> threads; //线程队列
std::queue<T> _task;//任务队列
pthread_mutex_t _mutex;
pthread_cond_t _cond;
int _capacity;
};
这里在编译的时候,会有个关于线程函数的报错信息,因为线程函数的参数为void *,函数返回值为void *,但当我们在类内写的时候,此时参数默认会有一个类的this指针,函数类型就匹配不上了,这里可以放在类外,或者修饰为静态成员函数。
主程序:
int main()
{
//刚开始,我们的线程池时刻就准备着了,有任务就开始处理
ThreadPool<Task> *p=new ThreadPool<Task>(20);
p->start();
srand(time(NULL)^getpid());
while(true)
{
//创建任务
int x = rand() % 10 + 1;
usleep(10);
int y = rand() % 5;
char op = opers[rand()%opers.size()];
Task t(x, y, op);
cout<<"hanldle task:"<<t.GetTask()<<endl;
//把任务将给线程池处理
p->Push(t);
sleep(1);
}
return 0;
}
单例模式
早在c++的时候我们就已经学过了单例模式,所谓的单例模式是一种设计模式,只允许有一个对象,单例模式分为饿汉与懒汉两种模式,区别就在于饿汉是直接创建这个对象,而懒汉是在用的时候才创建,两者无本质区别,主要是延迟创建了 。
我们今天简单的看一下饿汉与懒汉的代码:
饿汉方式实现单例模式
template <typename T>
class Singleton {
static T data;
public:
static T* GetInstance() {
return &data;
}
};
懒汉方式实现单例模式
template <typename T>
class Singleton {
static T* inst;
public:
static T* GetInstance() {
if (inst == NULL) {
inst = new T();
}
return inst;
}
};
而在线程中,考虑到线程安全的问题,我们一般都会把线程池封装成一个类,且只有一个对象。
懒汉方式实现单例模式(线程安全版本)
// 懒汉模式, 线程安全
template <typename T>
class Singleton {
volatile static T* inst; // 需要设置 volatile 关键字, 否则可能被编译器优化.
static std::mutex lock;
public:
static T* GetInstance() {
if (inst == NULL) { // 双重判定空指针, 降低锁冲突的概率, 提高性能.
lock.lock(); // 使用互斥锁, 保证多线程情况下也只调用一次 new.
if (inst == NULL) {
inst = new T();
}
lock.unlock();
}
return inst;
}
};
现在,我们就来将我们的线程池封装一下:
主要是禁用拷贝,赋值,获取对象指针,类外初始化,类内定义,注意设置为静态,私有。
struct ThreadInfo
{
ThreadInfo()
{}
ThreadInfo(pthread_t id,std::string Name):tid(id),name(Name)
{}
pthread_t tid;
std::string name;
};
static const int defaultnum =5;
template<class T>class ThreadPool
{
private:
void Lock()
{
pthread_mutex_lock(&_mutex);
}
void Unlock()
{
pthread_mutex_unlock(&_mutex);
}
void Wake()
{
pthread_cond_signal(&_cond);
}
void Wait()
{
pthread_cond_wait(&_cond,&_mutex);
}
std::string GetThreadName(pthread_t tid)
{
for (const auto &ti : threads)
{
if (ti.tid == tid)
return ti.name;
}
return "None";
}
public:
static void *Handler(void *args)
{
ThreadPool<T> *p = static_cast<ThreadPool<T> *>(args);
std::string name = p->GetThreadName(pthread_self());
while(true)
{
p->Lock();
//多个线程来处理任务,没有任务,阻塞队列等待
while(p->_task.empty())
{
p->Wait();
}
T t = p->Pop();
p->Unlock();
t();
std::cout << name << " run, "<< "result: " << t.GetResult() << std::endl;
}
}
void start()
{
for(int i=0;i<threads.size();i++)
{
threads[i].name="thread_"+std::to_string(1+i);
pthread_create(&(threads[i].tid),nullptr,Handler,this);
}
}
void Push(const T& temp)
{
//生产任务
Lock();
_task.push(temp);
//有任务了。可以唤醒线程
Wake();
Unlock();
}
T Pop()
{
T t=_task.front();
_task.pop();
return t;
}
~ThreadPool()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
static ThreadPool<T>* GetInstance()
{
if(_tp==nullptr)
{
_tp=new ThreadPool<T>();
}
return _tp;
}
private:
std::vector<ThreadInfo> threads; //线程队列
std::queue<T> _task;//任务队列
pthread_mutex_t _mutex;
pthread_cond_t _cond;
int _capacity;
static ThreadPool<T>* _tp;
private:
ThreadPool(int capacity=defaultnum):threads(capacity),_capacity(capacity)
{
pthread_cond_init(&_cond,nullptr);
pthread_mutex_init(&_mutex,nullptr);
}
ThreadPool(const ThreadPool<T> & temp)=delete;
const ThreadPool<T>& operator=(ThreadPool<T>& p)=delete;
};
//初始化对象
template<class T>
ThreadPool<T>* ThreadPool<T>:: _tp=nullptr
那如果在获取对象指针的时候是多线程并发访问,那么如何处理?
因此我们还需要加锁,创建全局的pthread_mutex_t:
//类内
static ThreadPool<T>* GetInstance()
{
if(_tp==nullptr)//在判断一次,只要被创建了,就没必要再有线程来申请了
{
pthread_mutex_lock(&_mutex_);
if(_tp==nullptr)
{
_tp=new ThreadPool<T>();
}
pthread_mutex_unlock(&_mutex_)
}
return _tp;
}
static pthread_mutex_t _mutex_;
//类外
template<class T>
pthread_mutex_t ThreadPool<T>::_mutex=PTHREAD_MUTEX_INITIALIZAR;