Linux--POSIX信号量--基于环形队列的生产消费模型-0208

本文介绍了信号量的概念,作为控制并发访问共享资源的工具,通过买票的例子解释了信号量的P操作(申请)和V操作(释放)。接着讲解了POSIX信号量的初始化、销毁、等待和发布等操作。文章还探讨了基于环形队列的单生产者单消费者模型,并展示了具体的C++代码实现。最后讨论了多生产多消费模型,强调了信号量在减少临界区内的判断和提高效率上的意义。
摘要由CSDN通过智能技术生成

1. 什么是信号量

共享资源由于原子性的原则,任何时刻都只有一个执行流在进行访问。表现为互斥,也就代表共享资源起始是被当做整体来访问的。

那如果有一个共享资源,不当成一个整体,让不同的执行流访问不同的资源区域代码,只有访问同一个区域的时候我们再进行同步或者互斥,这就做到了并发。

两个问题:a. 如何知道一共有几个资源,还剩多少个? b.如何确定一块资源是不是给你的呢?


我们以买票为例,买票的本质就是对座位资源的预定机制。

信号量本质是一个计数器,当线程需要访问临界空间时,需要先申请信号量,申请成功信号量--(预定资源 P操作),使用完毕信号量资源 ++(释放资源 V操作)。

信号量的使用就是我们先申请一个信号量,当前执行流一定具有一个资源可以被他所使用,我们保证一定留有资源给该线程使用,但给哪一个资源,具体需要程序员结合场景自定义编码完成。

2. POSIX 信号量

POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的

初始化信号量

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);

pshared:0表示线程间共享,非零表示进程间共享
value:信号量初始值

 销毁信号量

int sem_destroy(sem_t *sem);

等待信号量  P操作

int sem_wait(sem_t *sem); //P()

功能:等待信号量,会将信号量的值减1   P()操作


将信号量的值减 1,操作前,先检查信号量(sem)的值是否为 0;
若信号量为 0,此函数会阻塞,直到信号量⼤于 0 时才进⾏减 1 操作。

 发布信号量

int sem_post(sem_t *sem);//V()

功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1 V()操作

获取信号量的值

int sem_getvalue(sem_t *sem, int *sval);

获取 sem 标识的信号量的值,保存在 sval 中.
@param sem 信号量的地址.
@param sval 保存信号量值的地址.
@return 成功: 0; 失败: -1.

3. 基于环形队列的单生产者单消费者模型

3.1 基本概念

开始时,生产和消费指向同一位置,此时为空。过程中可能再次遇到生产和消费位于同一位置,此时为空或者为满。生成和消费大部分时间都不会在同一位置,所以我们需要让二者不在同一位置时并发访问,在同一位置时具有互斥同步关系就可以了。

对于生产者而言,他在意的资源是空间资源,即N(队列的长度),没有空间时无法申请。所以我们在一开始时,就可以将空间资源置成N,每次申请,该值就--。一旦申请成功,我们就生产出来了一个数据资源,所以数据资源++。

对于消费者而言,他在意的是数据资源,即C(队列的元素个数),没有元素时无法申请。由于这个元素是生产者生产出来的,所以在一开始时,就可以将数据资源置成0。一开始一定申请不成功,于是消费者线程被阻塞。每次申请成功,C--,而且意味着存储这块数据的空间资源可以被释放,空间资源++。


综上,我们有两个信号量,space_sem_ 代表空间资源,data_sem_ 代表数据资源。当环形队列插入数据时,space_sem_进行p()操作,data_sem_进行v()操作。

删除数据时,space_sem_进行v()操作,data_sem_进行p()操作。

 3.2 代码结构

后续贴出完整代码

环形队列逻辑

首先需要封装出来一个环形队列,ringqueue.hpp

 主函数逻辑

 信号量的封装

 3.3 具体代码

3.3.1 testmain.cc

#include "ringqueue.hpp"
#include <cstdlib>
#include <ctime> //整随机数的
#include <sys/types.h>
#include <unistd.h>

void* consumer(void* args)
{
    RingQueue<int>*rq=(RingQueue<int>*)args;
    while(true)
    {
        int x;
        //从环形队列中获取数据
        rq->pop(&x);
        std::cout<<"消费:"<<x<<"["<<pthread_self()<<"]"<<std::endl;
        sleep(1);
    }
}

void* productor(void* args)
{
    RingQueue<int> *rq = (RingQueue<int> *)args;
    while(true)
    {
        // sleep(1);

        // 1. 构建数据或者任务对象 -- 一般是可以从外部来 -- 不要忽略它的时间消耗问题
        int x = rand() % 100 + 1;
        std::cout << "生产:" << x << " [" << pthread_self() << "]" << std::endl;

        // 2. 推送到环形队列中
        rq->push(x); // 完成生产的过程
    }

}

int main()
{
    //设置随机数种子
    srand((uint64_t)time(nullptr)^getpid()); 
    //创建两个线程
    pthread_t c,p;
    //创建一个环形队列
    RingQueue<int>* rq=new RingQueue<int>();

    //为了让两个线程看到同一个环形队列
    pthread_create(&c,nullptr,consumer,(void*)rq);
    pthread_create(&p,nullptr,productor,(void*)rq);

    pthread_join(c,nullptr);
    pthread_join(p,nullptr);
    return 0;
}

 3.3.2 ringqueue.hpp

#ifndef _Ring_QUEUE_HPP_
#define _Ring_QUEUE_HPP_
#include <iostream>
#include <pthread.h>
#include <vector>
#include "sem.hpp"
const int g_default_num=5;//缺省值
template<class T>
class RingQueue
{
public:
    RingQueue(int default_num=g_default_num)
        :ringqueue_(default_num)
        ,num_(default_num)
        ,c_step(0)
        ,p_step(0)
        ,space_sem_(default_num)
        ,data_sem_(0)
    {}
    ~RingQueue()
    {}
    //生产者才push
    void push(const T& in)
    {
        //我们需要使用信号量对临界资源进行保护
        space_sem_.p();//申请空间
        ringqueue_[p_step++]=in;
        p_step%=num_;
        data_sem_.v();
    }
    //消费者才pop
    void pop(T* out)
    {
        data_sem_.p();
        *out=ringqueue_[c_step++];
        c_step%=num_;
        space_sem_.v();
    }

private:
    std::vector<T> ringqueue_;
    int num_;
    int c_step;//消费者的下标
    int p_step;//生产者的下标
    Sem space_sem_;
    Sem data_sem_;
};

#endif

3.3.3 测试结果

这里我让消费者每隔一秒消费一次,所以出现消费一个生产一个,且在消费完成前,生产会阻塞的现象。

 4. 多生产多消费模型

生产者们的临界资源是什么?p_step

消费者们的临界资源是什么?c_step

我们要确保这两个资源是原子性的,所以我们需要在生成者和生产者之间,以及消费者和消费者之间加两把锁。

 4.1 先申请信号量还是先申请锁?

信号量表征资源数量的多少,如果我们先申请信号量就相当于先分配好了任务。当一个线程进入了临界资源区时,其他线程可以先竞争信号量,然后只需要等待申请锁成功。

就好比先买票,然后排队入场。和先排队入场,再买票。

4.2 测试结果

5. 多生产多消费 和 信号量的意义

上述程序,虽然是多生产多消费,但只有一个在生产一个在消费,和单生产单消费没有什么区别。多生产多消费的意义是什么呢?

消费的本质:把公共空间的任务变成私有的。

将数据生产前和拿到之后的处理才是最耗费时间的。即拿任务虽然是一个一个拿的,但是我们处理任务是并发的。


信号量的意义是,可以不用进入临界区,就知道资源的使用情况,甚至可以减少临界资源内部的判断(买票是不是还有票,没有信号量就可能会造成疯狂的申请锁释放锁,但是不做事,所以资源不就位就应该挂起等待)。

信号量需要提前预设资源的情况,并且在p(),v()变化中,我们可以在外部就找到临界资源你的情况。信号量可以帮我们提前了解资源情况。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值