POSIX信号量
摘要:本文介绍了信号量在生产者-消费者模型中的应用,重点分析了基于环形队列的实现方式。文章首先讲解了POSIX信号量的相关函数(sem_init、sem_wait、sem_post等),然后详细阐述了环形队列的生产消费模型原理,包括如何通过信号量实现同步与互斥。最后给出了代码实现,展示了生产者申请空间信号量、消费者申请数据信号量的具体过程,并讨论了多生产者多消费者情况下的锁机制。实现中采用先申请资
目录
POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。但POSIX可以用于线程间同步。
一:信号量相关函数
初始化信号量:
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
pshared:0表⽰线程间共享,⾮零表示进程间共享
value:信号量初始值
销毁信号量:
int sem_destroy(sem_t *sem);
等待信号量:
功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()
发布信号量:
功能:发布信号量,表⽰资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);//V()
上篇文章的生产者-消费者的例子是基于queue的,其空间可以动态分配,现在基于固定大小的环形队列重写这个程序(POSIX信号量):
二:基于环形队列的生产消费模型
环形队列采用数组模拟,用模运算来模拟环状特性:
其中环形队列默认是空和满的head和tail都是指向同一个位置的,解决办法:
1.使用一个计数器 2.对开一个位置,空指向开头,tail的next == head,表示满

但是我们现在有信号量这个计数器,就很简单的进行多线程间的同步过程。
关于信号量的详细讲解:system V共享内存-CSDN博客
环形队列中每一个资源都用信号量表示,任何人访问临界资源之前,都要申请信号量
假设初始空间为:N,数据为:0(两个信号量)
对于生产者:看剩余空间
int tail = 0;
P(空间)
//给生产者空间,放入数据
ring[tail] = data;
tail++;
V(数据)
对于生产者:看剩余数据
int head = 0;
P(数据)
head++;
V(空间)
1.极端情况下生产者和消费者访问同一个位置
1.1为空:保证生产者原子性先生产
1.2为满:保证消费者原子性先消费
位置的空满体现了互斥与同步,具有访问先后顺序(通过信号量完成的互斥和同步)
2.不是极端情况下:
生产者和消费者一定不是同一个位置,可以同时进行并发访问。
即:同一个位置互斥同步,不是同一个位置并发
以上是基于单个生产者单个消费者,如果是多个生产者多个消费者呢?
生产和生产,消费和消费之间是互斥的,加2把锁,一次只能一个消费者和一个生产者访问环形队列,而生产和消费之间的关系由信号量维护
三:代码实现:
是先P资源还是先申请锁?
从效果实现来说,两个都可以保证实现同一个效果,但是先申请资源再申请锁更好
如果申请锁,再申请资源,如果资源不够,那一个线程就在阻塞等待,其他线程因为拿不到锁,在所的地方就等待,可能要进行两次等待,稍微慢一点
如果先申请资源,将所以满足条件的线程放进去,然后集体再争夺锁,这样争夺到锁,就可以直接使用资源,然后再释放锁,让其他线程使用了。
先申请资源再竞争锁,就好比限定人数的场子,先买限定数量票,然后让有票的人排队进入场子,只需要在验票处等待即可,票卖完之后,多余的人就不能排队验票进;如果是卖一个人验一张票,可能在卖票的地方就会拥堵
封装信号量:
#pragma once
#include <semaphore.h>
namespace SemModule
{
int defaultsemval = 0;
class Sem
{
public:
Sem(int value = defaultsemval) : _init_val(value)
{
int n = ::sem_init(&_sem, 0, _init_val);
(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_val;
};
}
环形队列生产消费模型实现:
#pragma once
#include <iostream>
#include <vector>
#include "Sem.hpp"
#include "Mutex.hpp"
namespace RingBufferModule
{
using namespace MutexModule;
using namespace SemModule;
template <typename T>
class RingBuffer
{
public:
RingBuffer(int cap)
:_cap(cap)
,_p_step(0)
,_c_step(0)
,_ring(cap)
,_datasem(0)
,_spacesem(cap)
{
}
//为什么不需要进行条件判断
//信号量本身表示资源数目
//只要P成功就表示一定有资源,不需要判断;如果不成功P就会失败,线程进入等待
void Equeue(const T& in)
{
//生产者
_spacesem.P();//申请空间,消耗空间
{
LockGuard lock(_p_lock);
_ring[_p_step] = in;
_p_step++;
_p_step %= _cap;
}
_datasem.V();//生产数据,释放数据
}
void Pop(T *out)
{
//消费者
_datasem.P();
{
LockGuard lock(_p_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 _c_lock;
Mutex _p_lock;
};
}
更多推荐


所有评论(0)