基于环形队列的生产消费模型
- 环形队列采⽤数组模拟,用模运算来模拟环状特性
- 原来的方法: 环形结构起始状态和结束状态都是⼀样的,不好判断为空或者为满,所以可以通过加计数器或者 标记位来判断满或者空。另外也可以预留⼀个空的位置,作为满的状态
- 但是我们现在有信号量这个计数器,就很简单的进行多线程间的同步过程
单⽣产,单消费
多⽣产,多消费
"321":
3: 三种关系
a: ⽣产和消费互斥和同步
b: ⽣产者之间: 互斥
c: 消费者之间: 互斥
解决⽅案:加锁
// 1. 需要⼏把锁?2把
// 2. 如何加锁
单生产单消费
生产者只关心还有没有剩余空间资源 ,消费者只关心还有没有剩余数据资源
需要两个信号量(一个申请空间资源 ,一个申请数据资源 )进行维护生产者消费者之间的互斥与同步
情况分析:
1. 生产者消费者访问同一个位置时 ,要么是空 要么是满
- 空: 数据个数为0 ,消费者无法申请数据(无法进入临界区) 生产者可以原子性的生产
- 满: 空间个数为 0,生产者无法申请空间(无法进入临界区) 消费者可以原子性的消费
此时生产者与消费者是串行
2.生产者消费者不访问同一个位置 ,不为空不为满
此时生产者和消费者是并发
主体逻辑:
为什么Enqueue和Pop中没有判断?
前面我们写BlockQueue时, 用互斥量维护生产者消费者之间的互斥关系,申请了锁(mutex)后还要判断
用信号量维护生产者消费者之间的互斥关系,就不用判断
为什么我们用信号量实现互斥 ,不需要判断?
因为互斥量(mutex)是对资源整体申请(queue) ,可以认为申请成功就是有使用整体的权限 , 但是里面的数据资源情况未知 , 能不能使用 就需要判断 (不为空就是有数据资源 ,不为满就是有空间资源)
而用信号量(sem) 是对资源局部申请 ,只要申请成功(局部一定是未使用的)即可使用
在CP模型中 ,datasem是数据资源申请 ,spacesem是空间资源申请 ,进而无需判断
两种信号量代表两种资源,互斥量只是给予了进入资源的权力,资源的状态还需判断
总结:
- 信号量将资源是否可用转移成了 只要信号量申请成功即可.
- 互斥量只是提供了资源进入的权限,但不一定资源可用 ,所以要判断
RingBuffer.hpp
#pragma once
#include<iostream>
#include<vector>
#include"Sem.hpp"
namespace RingBufferModule
{
using namespace SemModule;
template<typename T>
class RingBuffer
{
public:
RingBuffer(int cap)
:_ring(cap)
,_cap(cap)
,_c_pos(0)
,_p_pos(0)
,_datasem(0)
,_spacesem(cap)
{}
void Enqueue(const T& in)
{
_spacesem.P();//申请空间
_ring[_p_pos]=in;
_p_pos++;
_p_pos%=_cap;
_datasem.V();
}
void Pop(T* out)
{
_datasem.P();
*out =_ring[_c_pos];
_c_pos++;
_c_pos%=_cap;
_spacesem.V();
}
~RingBuffer()
{}
private:
std::vector<T> _ring;
int _cap; //总容量
int _c_pos; //消费者位置
int _p_pos; //生产者位置
Sem _datasem; //数据信号量
Sem _spacesem; //空间信号量
};
}
Sem.hpp
#pragma once
#include<semaphore.h>
namespace SemModule
{
int defalutsemval =1;
class Sem
{
public:
Sem(int value = defalutsemval)
:_init_value(value)
{
int n= sem_init(&_sem ,0 ,_init_value);
}
void P()
{
int n =sem_wait(&_sem);
}
void V()
{
int n=sem_post(&_sem);
}
~Sem()
{
int n= sem_destroy(&_sem);
}
private:
sem_t _sem;
int _init_value;
};
}
testRingBuffer.cc
#include"RingBuffer.hpp"
#include<pthread.h>
#include<unistd.h>
using namespace RingBufferModule;
void* Product(void* args)
{
RingBuffer<int>* rb =static_cast<RingBuffer<int>*> (args);
int data =0;
while(true)
{
//1.获取数据
//2.生产数据
rb->Enqueue(data);
std::cout<<"生产了一个数据"<<data<<std::endl;
data++;
}
}
void* Consumer(void* args)
{
RingBuffer<int>* rb =static_cast<RingBuffer<int>*> (args);
while(true)
{
sleep(1);
// 1. 消费数据
int data;
rb->Pop(&data);
// 2. 处理:花时间
std::cout << "消费了一个数据: " << data << std::endl;
}
}
int main ()
{
RingBuffer<int>* rb = new RingBuffer<int>(5);
pthread_t c ,p;
pthread_create(&p,nullptr,Product ,rb);
pthread_create(&c,nullptr,Consumer ,rb);
pthread_join(p , nullptr);
pthread_join(c, nullptr);
return 0;
}
多生产多消费
单生产单消费 已经将生产者与消费者的互斥与同步实现了
只需要再实现 生产者与生产者之间的互斥关系 和消费者与消费者的互斥关系即可
实现方法:
用两把锁 从多个生产者中挑选一个生产者 ,从多个消费者中挑选一个消费者即可
现在有一个问题 ,从多个生产者中挑选一个生产者的申请锁的位置在申请信号量之前还是之后?
之前之后锁都能使得生产者(消费者)之间互斥 , 但是在申请信号量之后申请锁 ,可以提高效率
因为一旦有生产者申请到锁后可以直接进行获取数据 ,生产数据 ,申请信号量的工作已经在申请锁前做完了,在临界区内的时间就会变少.
Mutex.hpp
#pragma once
#include<iostream>
#include<pthread.h>
#include <unistd.h>
namespace LockModule
{
class Mutex
{
public:
//防止锁被拷贝
Mutex(const Mutex&) = delete;
const Mutex& operator = (const Mutex&) = delete;
Mutex()
{
int n = ::pthread_mutex_init(&_lock ,nullptr);
if(n!= 0) std::cout<<"pthread_mutex_init error"<<std::endl;
}
void Lock()
{
int n = ::pthread_mutex_lock(&_lock);
if(n!= 0) std::cout<<"lock error"<<std::endl;
}
void Unlock()
{
int n =::pthread_mutex_unlock(&_lock);
if(n!= 0) std::cout<<"unlock error"<<std::endl;
}
pthread_mutex_t* LockPtr()
{
return &_lock;
}
~Mutex()
{
int n = pthread_mutex_destroy(&_lock);
if(n!= 0) std::cout<<"destroy error"<<std::endl;
}
private:
pthread_mutex_t _lock;
};
class LockGuard
{
public:
LockGuard(Mutex &mtx):_mtx(mtx)
{
_mtx.Lock();
}
~LockGuard()
{
_mtx.Unlock();
}
private:
Mutex &_mtx;
};
}
RingBuffer.hpp
#pragma once
#include<iostream>
#include<vector>
#include"Sem.hpp"
#include"Mutex.hpp"
namespace RingBufferModule
{
using namespace SemModule;
using namespace LockModule;
template<typename T>
class RingBuffer
{
public:
RingBuffer(int cap)
:_ring(cap)
,_cap(cap)
,_c_pos(0)
,_p_pos(0)
,_datasem(0)
,_spacesem(cap)
{}
void Enqueue(const T& in)
{
_spacesem.P();//申请空间
{
LockGuard lockguard(_p_mutex);
_ring[_p_pos]=in;
_p_pos++;
_p_pos%=_cap;
}
_datasem.V();
}
void Pop(T* out)
{
_datasem.P();
{
LockGuard lockguard(_c_mutex);
*out =_ring[_c_pos];
_c_pos++;
_c_pos%=_cap;
}
_spacesem.V();
}
~RingBuffer()
{}
private:
std::vector<T> _ring;
int _cap; //总容量
int _c_pos; //消费者位置
int _p_pos; //生产者位置
Sem _datasem; //数据信号量
Sem _spacesem; //空间信号量
Mutex _c_mutex; //消费者之间的锁
Mutex _p_mutex; //生产者之间的锁
};
}