一.System V信号量
可以看往期博客(了解即可,怎么使用的根据兴趣)
太麻烦了,我们直接使用POSIX信号量
二.POSIX信号量
1.信号量的讲解


我们在进行写抢票代码的时候,将我们对应的票,当作我们对应的整体使用

所以,互斥锁本身就是信号量的一种

2.POSIX信号量的接口




三.环形队列的生产消费模型
1.讲解环形队列












如果我们要设计一个环形队列的话,我们应该有两个信号量(一个data,一个space)

2.环形队列整体调用代码
cpp
#include "RingBuffer.hpp"
#include <pthread.h>
#include <unistd.h>
#include <ctime>
using namespace RingBufferModule;
void* Consumer(void* args)
{
RingBUffer<int>* ring_buffer = static_cast<RingBUffer<int>*>(args);
while(true)
{
int data;
ring_buffer->Pop(&data);
}
}
void* Productor(void* args)
{
RingBUffer<int>* ring_buffer = static_cast<RingBUffer<int>*>(args);
int data = 10;
while(true)
{
ring_buffer->Equeue(10);
}
}
int main()
{
RingBUffer<int>* ring_buffer = new RingBUffer<int>();//共享资源 -> 临界资源
//单生产,单消费
pthread_t c1,p1;
pthread_create(&c1,nullptr,Consumer,ring_buffer);
pthread_create(&p1,nullptr,Productor,ring_buffer);
pthread_join(c1,nullptr);
pthread_join(p1,nullptr);
delete ring_buffer;
return 0;
}

3.环形队列框架设计
cpp
#pragma once
#include <iostream>
#include <vector>
#include <semaphore.h>
namespace RingBufferModule
{
template<class T>
class RingBUffer
{
public:
RingBuffer()
{
}
void Equeue(const T& in)
{
//生产者
}
void Pop(T* out)
{
//消费者
}
~RingBuffer()
{
}
private:
std::vector<T> _ring; //环,临界资源
int _cap; //容量
int _p_step; //生产者位置
int _c_step; //消费者位置
sem_t _datasem; //数据信号量
sem_t _spacesem; //空间信号量
};
}
4.Sem封装
cpp
#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)n;
}
void P()
{
int n = ::sem_wait(&_sem);//就是--
(void)n;
}
void V()
{
int n = ::sem_post(&_sem);//就是++
(void)n;
}
~Sem()
{
int n = ::sem_destroy(&_sem);
(void)n;
}
private:
sem_t _sem;
int _init_value;
};
} // namespace SemModule
5.RingBuffer构造函数
cpp
RingBuffer(int cap):
_ring(cap),
_cap(cap),
_p_step(0),
_c_step(0),
_datasem(0),
_spacesem(cap)
{
}
6.Equeue函数
cpp
void Equeue(const T& in)
{
//生产者
_spacesem.P();
_ring[_p_step] = in;
_P_step++;
_p_step %= _cap;
_datasem.V();
}
7.Pop函数
cpp
void Pop(T* out)
{
//消费者
_datasem.P();
*out = _ring[_c_step];
_c_step++;
_c_step %= _cap;
_spacesem.V();
}
8.总代码
cpp
#pragma once
#include <iostream>
#include <vector>
#include "Sem.hpp"
namespace RingBufferModule
{
using namespace SemModule;
template<class T>
class RingBuffer
{
public:
RingBuffer(int cap):
_ring(cap),
_cap(cap),
_p_step(0),
_c_step(0),
_datasem(0),
_spacesem(cap)
{
}
void Equeue(const T& in)
{
//生产者
_spacesem.P();
_ring[_p_step] = in;
_P_step++;
_p_step %= _cap;
_datasem.V();
}
void Pop(T* out)
{
//消费者
_datasem.P();
*out = _ring[_c_step];
_c_step++;
_c_step %= _cap;
_spacesem.V();
}
~RingBuffer()
{
}
private:
std::vector<T> _ring; //环,临界资源
int _cap; //容量
int _p_step; //生产者位置
int _c_step; //消费者位置
Sem _datasem; //数据信号量
Sem _spacesem; //空间信号量
};
}

就相当于是,将我们对应得mutex和cond进行了封装
四.测试
1.单生产,单消费
cpp
#include "RingBuffer.hpp"
#include <pthread.h>
#include <unistd.h>
#include <ctime>
using namespace RingBufferModule;
void* Consumer(void* args)
{
RingBuffer<int>* ring_buffer = static_cast<RingBuffer<int>*>(args);
while(true)
{
sleep(1);
int data;
ring_buffer->Pop(&data);
std::cout << "消费了一个数据: " << data << std::endl;
}
}
void* Productor(void* args)
{
RingBuffer<int>* ring_buffer = static_cast<RingBuffer<int>*>(args);
int data = 0;
while(true)
{
ring_buffer->Equeue(data);
std::cout << "生产了一个数据: " << data << std::endl;
data++;
}
}
int main()
{
RingBuffer<int>* ring_buffer = new RingBuffer<int>(5);//共享资源 -> 临界资源
//单生产,单消费
pthread_t c1,p1;
pthread_create(&c1,nullptr,Consumer,ring_buffer);
pthread_create(&p1,nullptr,Productor,ring_buffer);
pthread_join(c1,nullptr);
pthread_join(p1,nullptr);
delete ring_buffer;
return 0;
}


为啥我们在这个代码里面,没有进行判断呢?

他将资源是否可使用,转化成是否申请成功信号量(只要申请成功就一定有资源),要等待也是在信号量上面进行等待
生产者和消费者之间的关系,由我们对应的信号量来进行维护了
2.多生产,多消费
生产者之间要加锁,由一个生产者为代表,来进入我们对应的环形队列里面(消费者也是同理)
修改代码如下:
cpp
#pragma once
#include <iostream>
#include <vector>
#include "Sem.hpp"
namespace RingBufferModule
{
using namespace SemModule;
template<class T>
class RingBuffer
{
public:
RingBuffer(int cap):
_ring(cap),
_cap(cap),
_p_step(0),
_c_step(0),
_datasem(0),
_spacesem(cap)
{
pthread_mutex_init(&_p_lock);
pthread_mutex_init(&_c_lock);
}
void Equeue(const T& in)
{
//生产者
pthread_mutex_lock(&_p_lock);
_spacesem.P();
_ring[_p_step] = in;
_P_step++;
_p_step %= _cap;
_datasem.V();
pthread_mutex_unlock(&_p_lock);
}
void Pop(T* out)
{
pthread_mutex_lock(&_c_lock);
//消费者
_datasem.P();
*out = _ring[_c_step];
_c_step++;
_c_step %= _cap;
_spacesem.V();
pthread_mutex_unlock(&_c_lock);
}
~RingBuffer()
{
pthread_mutex_destroy(&_p_lock);
pthread_mutex_destroy(&_c_lock);
}
private:
std::vector<T> _ring; //环,临界资源
int _cap; //容量
int _p_step; //生产者位置
int _c_step; //消费者位置
Sem _datasem; //数据信号量
Sem _spacesem; //空间信号量
pthread_mutex_t _p_lock; //生产者之间的锁
pthread_mutex_t _c_lock; //消费者之间的锁
};
}


先申请信号量,再申请锁,还是先申请锁还是先信号量呢?
答案是先进行申请信号量,拿这信号量,再竞争锁资源(这样较快)
3.封装代码
cpp
#pragma once
#include <iostream>
#include <vector>
#include "Sem.hpp"
#include "Mutex.hpp"
namespace RingBufferModule
{
using namespace SemModule;
using namespace LockModule;
template<class T>
class RingBuffer
{
public:
RingBuffer(int cap):
_ring(cap),
_cap(cap),
_p_step(0),
_c_step(0),
_datasem(0),
_spacesem(cap)
{
}
void Equeue(const T& in)
{
//生产者
_spacesem.P();
{
LockGuard lockguard(_p_lock);
_ring[_p_step] = in;
_P_step++;
_p_step %= _cap;
}
_datasem.V();
}
void Pop(T* out)
{
//消费者
_datasem.P();
{
LockGuard lockguard(_c_lock);
*out = _ring[_c_step];
_c_step++;
_c_step %= _cap;
}
_spacesem.V();
}
~RingBuffer()
{
}
private:
std::vector<T> _ring; //环,临界资源
int _cap; //容量
int _p_step; //生产者位置
int _c_step; //消费者位置
Sem _datasem; //数据信号量
Sem _spacesem; //空间信号量
Mutex _p_lock; //生产者之间的锁
Mutex _c_lock; //消费者之间的锁
};
}
