一.信号量
1.对比:
Systme V标准中有信号量的概念 , POSIX标准中也有 , 他俩仅有细微的差别
- Systme V标准中的信号量用于进程间的同步.
- POSIX标准中的信号量默认用于线程间的同步.
二者本质是相同的 , 都是一个计数器 , 代表有多少剩余资源可以使用 .
2.POSIX信号量函数接口:
头文件 :
<semaphore.h>
sem_init:
int sem_init(sem_t *sem, int pshared, unsigned int value);
-
返回值 : 成功为0 , 失败为-1
-
参数一 : 信号量类型
sem_t的指针 -
参数二 : 模式 , 0为线程间共享 , 1为进程间共享(因为Linux下线程是轻量级进程,二者界限比较模糊)
-
参数三 : 规定最大容纳多少个资源.
-
调用逻辑: 初始化一个信号量变量.
sem_wait:
int sem_wait(sem_t *sem);
-
返回值 : 成功为0 , 失败为-1;
-
参数 : 信号量类型
sem_t的指针 -
调用逻辑 : 类似于计数器-- , 只不过这个过程是原子的!!! 若计数器归零 , 开始等待.
sem_post:
int sem_post(sem_t *sem);
-
返回值 : 成功为0 , 失败为-1;
-
参数一 : 信号量类型
sem_t的指针 -
调用逻辑 : 类似于计数器++ , 过程也是原子的!!! 若计数器的值达到上限 , 开始等待.
sem_destroy:
int sem_destroy(sem_t *sem);
-
返回值 : 成功为0 , 失败为-1
-
参数一 : 信号量类型
sem_t的指针 -
调用逻辑: 销毁一个信号量变量.
二.基于RingQueue和Sem的C_P模型代码实现
1.环形队列:
- 作为一种数据结构 , 在保证先进先出 的性质下 , 还能复用旧空间 .
- 先进先出 正好符合线程互斥的原则.
- 旧空间的复用 正好符合多线程并发的需求.
使用vector作为环形作为底层容器!vector作为RingQueue底层容器的依据\]- 1. 首先 , vector随机访问效率很高 ,不用说 . 2. 最重要的是 , vector的**下标访问** 天然满足并发需求!!! \*生产者和消费者可以通过vector下标独立的访问容器的不同位置 , 不会被诸如List底层的指针限制 . \* #Linux/线程/vector_RingQueue
其中,信号量就类似于避免头尾指针在运行过程中相遇所使用的计数器 , 因此不用额外开辟一个空间

2.基本类(RingQueue)框架 :
成员变量:
vector<T> _ringQueue:消费者和生产者都可以访问的交易市场(共享资源).int _cap: 人为规定的共享资源的最大容量 .Sem _csem: 消费者的信号量 , 记录剩下多少可使用资源.Sem _psem: 生产者的信号量 , 记录剩下多少空闲位置.int _cstep: 消费者的消费位置 , 即向vector里添加元素时的下标.int _pstep: 生产者的生产位置 , 即向vector里获取元素时的下标.
成员函数:RingQueue(): 构造函数 , 除了初始化成员变量外 , 也调用resize()为vector提前开辟好空间void Consume(): 消费者消费一次的逻辑.void Produce(const T &product): 生产者生产一次的逻辑.
3.单生产,单消费:
RingQueue:
c++
#pragma once
#include<vector>
#include"Cond.hpp"
#include"Mutex.hpp"
#include"Sem.hpp"
using namespace Cond_module;
using namespace Mutex_module;
using namespace Semaphore_module;
template<typename T>
class RingQueue
{
public:
static const int max_cap = 5;
RingQueue()
:_cap(max_cap)
,_cstep(0)
,_pstep(0)
,_csem(0)
,_psem(max_cap)
{
_ringQueue.resize(_cap);
}
void Consume()
{
_csem.Wait();
auto ret = _ringQueue[_cstep++];
_cstep %= _cap;
_psem.Post();
}
void Produce(const T& product)
{
_psem.Wait();
auto ret = _ringQueue[_pstep++];//Linux下非得接受下标访问的返回值
(void)ret; //但是为了避免返回值没被使用的警告,强行使用!!!
_pstep %= _cap;
_csem.Post();
}
~RingQueue()
{
}
private:
std::vector<T> _ringQueue;
int _cap;
// int _surplus;
// int _space;
Sem _csem;
Sem _psem;
int _cstep;//消费者的消费位置
int _pstep;//生产者的生产位置
Sem _sem; //确保生产者和消费者的同步关系;
};
Main.cpp
c++
#include"RingQueue.hpp"
#include<unistd.h>
template<typename T>
void* Consumer(void* input)
{
auto rq = static_cast<RingQueue<T>*>(input);
while(true)
{
sleep(1);
rq->Consume();
std::cout << "Consume()!!!\n" ;
}
return nullptr;
}
template<typename T>
void* Producer(void* input)
{
auto rq = static_cast<RingQueue<T>*>(input);
while(true)
{
rq->Produce(1);
std::cout << "Producer()!!!\n" ;
}
return nullptr;
}
int main()
{
pthread_t tid1 = 0;
pthread_t tid2 = 0;
RingQueue<int>* rq = new RingQueue<int>;
pthread_create(&tid1,nullptr,Consumer<int>,(void*)rq);
pthread_create(&tid1,nullptr,Producer<int>,(void*)rq);
while(true) { sleep(1); }
pthread_join(tid1,nullptr);
pthread_join(tid2,nullptr);
return 0;
}
4.多生产,多消费:
单生产单消费中以及实现了消费者和生产者 之间的同步 , 接下来是消费者之间 以及生产者之间的同步.
加上两把锁 即可 : Mutex _cmut 和 Mutex_pmut一把用于消费者之间 , 另一把用于生产者之间
RingQueue(加入两把锁即可):
c++
void Consume()
{
_csem.Wait();
{
LockGuard lg(_cmut); //RAII风格的互斥锁
auto ret = _ringQueue[_cstep++];
_cstep %= _cap;
_psem.Post();
}
}
void Produce(const T &product)
{
_psem.Wait();
{
LockGuard lg(_pmut);//RAII风格的互斥锁
auto ret = _ringQueue[_pstep++];
(void)ret;
_pstep %= _cap;
_csem.Post();
}
}
锁和信号量的先后???
上面的代码里将加锁放在信号量的等待之前是有讲究的 .
可以这样理解:
锁保证了线程间串行 , 就好像电影院门口排队的人群 ;
信号量保证了线程间数据的一致性 , 就好像排队人群手里的票;
**先买票了再去排队好,还是先排队后再买票???** **答案毋庸置疑是 : 先买票了在排队 .**同理 : 让线程先通过信号量预定资源的使用权 (买票) , 等拿到锁排队到自己 后才能真正使用资源 (进入电影院) .从而提高效率.
三. BlockQueue和RingQueue的本质差别:
1.底层数据结构 :
对于BlockQueue :
底层咱用的是容器适配器
queue, 而他的底层又封装了容器de_queue .对数据的修改本质是通过底层维护的指针 , 而指针没法顺应生产者和消费者二者异步访问的需求 . 因为没法让指针在一个数据结构里指来指去.
因此只能把公共资源看做一个整体 .
对于RingQueue :底层咱用的是容器
vector, 它的本质是一个更灵活的数组 .此时对数据的修改取决于生产者和消费者传入的下标 , 可以异步访问 . 因为使用不同的下标自然访问不通的位置.
因此可以把公共资源划分成很多部分.
2.条件变量和信号量:
对于条件变量Cond:
只能允许一方访问 --- 生产者或者消费者 , 以至于二者不能同时进行.
对于信号量semaphore可以允许两方访问 --- 生产者和消费者异步进行 , 各自的信号量又保证了在访问条件不满足时进行等待
转化!!!只要信号量中的资源数量设置为1 , 那就退化成了条件变量!!!