Linux信号量以及基于环形队列的生产者消费者模型

文章目录

  • 信号量
    • 信号量的接口
      • 初始化
      • 销毁
      • 等待信号量
      • 发布信号量
  • 环形队列
    • 结合信号量设计模型
  • 实现基于环形队列的生产者消费者模型
    • Task.hpp
    • RingQueue.hpp
    • main.cc
    • 效果
    • 对于多生产多消费的情况

信号量

信号量的本质是一个计数器

首先一份公共资源在实际情况中可能存在不同的线程可以并发访问不同的区域。因此当一个线程想要访问临界资源时,可以先申请信号量,只要信号量申请成功就代表着这个线程在未来一定能访问临界资源的一部分。而这个申请信号量的本质就是对临界资源中特定的小块资源进行预定机制

所有的线程都能看到信号量,也就代表着信号量的本身就是公共资源如果信号量申请失败,说明该线程并不满足条件变量则线程进入阻塞等待

信号量的操作可以称为 PV操作,申请信号量成功则信号量的值减1(P),归还资源后信号量的值加1(V)。在这过程中一定要保证原子性

信号量的接口

信号量的类型为:sem_t

初始化

定义一个信号量并初始化:

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

参数一为信号量的地址

参数二如果设为0则表示线程间共享,非0则表示进程间共享

参数三为信号量的初始值,也就是这个计数器的最大值

初始化成功返回0

销毁

int sem_destroy(sem_t *sem);

参数为信号量的地址

等待信号量

也就是申请信号量,等待成功则信号量的值减1

int sem_wait(sem_t *sem);

参数为信号量的地址

发布信号量

也就是归还信号量,发布成功则信号量的值加1

int sem_post(sem_t *sem);

参数为信号量的地址

环形队列

将生产者消费者模型放到环形队列里该如何理解呢?

首先环形队列可以理解为就是指一个循环的数组。而对于生产者和消费者而言,生产者负责往这个数组中放入数据,消费者负责从这个数组中拿取数据

对于生产者而言:只有环形队列中有空的空间时才可以放数据

对于消费者而言:环形队列中必须要有不为空的空间时才能拿数据

那么肯定是生产者放入数据后消费者才能拿数据,因此在唤醒队列中消费者永远都不能超过生产者

假如生产者和消费者都指向了同一块空间时,要么队列满了,要么队列为空

如果队列满了,那么消费者就必须要先运行拿走数据后生产者才可以运行

如果队列为空,那么生产者必须放入数据后消费者才能运行拿数据

如果为其他情况,说明生产者和消费者所指向的空间是不一样的

image-20230717154905990

结合信号量设计模型

那么根据上述的消费者和生产者不同的特性,结合信号量为两者设计条件

对于生产者而言看中的是队列中剩余的空间,那么可以给生产者设置信号量为队列的总容量

对于消费者而言看中的是队列中不为空的空间,则可以给消费者初始的信号量值设为0

那么对于生产过程而言:

  1. 首先生产者去申请信号量也就是 P 操作
  2. 申请成功则往下继续生产操作的执行,申请失败则阻塞等待
  3. 执行完生产操作后,V操作,注意此时的V操作并不是对生产者的信号量操作,而是对消费者的信号量操作。因为此时队列中已经有了不为空的空间,所以消费者的信号量就可以进行加1操作,这样消费者才能申请成功信号量

对于消费过程而言:

  1. 消费者申请信号量
  2. 申请成功则往下继续消费操作的执行,失败则阻塞等待
  3. 执行完消费操作后,V操作,注意此时V操作是对生产者的信号量操作,因为消费完成后,队列里就多了一个空的空间,生产者的信号量就可以进行加1操作

那么对于生产者和消费者的位置其实就是队列中的下标,并且一定是有两个下标,如果队列为空为满,那么两者的位置相同

实现基于环形队列的生产者消费者模型

Task.hpp

#include <iostream>
#include <string>
#include <functional>
#include <cstdio>

// 负责计算的任务类
class CPTask
{
    // 调用的计算方法,根据传入的字符参数决定
    typedef std::function<int(int, int, char)> func_t;

public:
    CPTask()
    {
    }

    CPTask(int x, int y, char op, func_t func)
        : _x(x), _y(y), _op(op), _func(func)
    {
    }

    // 实现传入的函数调用
    std::string operator()()
    {
        int count = _func(_x, _y, _op);

        // 将结果以自定义的字符串形式返回
        char res[2048];
        snprintf(res, sizeof res, "%d %c %d = %d", _x, _op, _y, count);
        return res;
    }

    // 显示出当前传入的参数
    std::string tostring()
    {
        char res[1024];
        snprintf(res, sizeof res, "%d %c %d = ", _x, _op, _y);
        return res;
    }

private:
    int _x;
    int _y;
    char _op;
    func_t _func;
};

// 负责计算的任务函数
int Math(int x, int y, char c)
{
    int count;
    switch (c)
    {
    case '+':
        count = x + y;
        break;
    case '-':
        count = x - y;
        break;
    case '*':
        count = x * y;
        break;
    case '/':
    {
        if (y == 0)
        {
            std::cout << "div zero" << std::endl;
            count = -1;
        }
        else
            count = x / y;
        break;
    }
    default:
        break;
    }

    return count;
}

class SCTask
{
    // 获取保存数据的方法
    typedef std::function<void(std::string)> func_t;

public:
    SCTask()
    {
    }

    SCTask(const std::string &str, func_t func)
        : _str(str), _func(func)
    {
    }

    void operator()()
    {
        _func(_str);
    }

private:
    std::string _str;
    func_t _func;
};

RingQueue.hpp

#pragma once

#include <iostream>
#include <pthread.h>
#include <vector>
#include <semaphore.h>
#include <string>
#include <ctime>
#include <unistd.h>
#include <cassert>

using namespace std;

template <class T>
class RingQueue
{
public:
    RingQueue(const int size = 10)
        : _size(size)
    {
        // 初始化信号量以及数组大小
        // 生产者的信号量初始为数组大小
        // 消费者的信号量初始为0
        _Rqueue.resize(_size);
        assert(sem_init(&_ps, 0, _size) == 0);
        assert(sem_init(&_cs, 0, 0) == 0);
    }

    void push(const T &in)
    {
        // 生产者申请信号量
        assert(sem_wait(&_ps) == 0);

        // 为了模拟环形,下标需要模等于数组的大小
        // 保证下标不越界
        _Rqueue[_Pindex++] = in;
        _Pindex %= _size;

        // 消费者发布信号量
        assert(sem_post(&_cs) == 0);
    }

    void pop(T *out)
    {
        // 消费者申请信号量
        assert(sem_wait(&_cs) == 0);

        // 为了模拟环形,下标需要模等于数组的大小
        // 保证下标不越界
        *out = _Rqueue[_Cindex++];
        _Cindex %= _size;

        // 生产者发布信号量
        assert(sem_post(&_ps) == 0);
    }

    ~RingQueue()
    {
        sem_destroy(&_ps);
        sem_destroy(&_cs);
    }

private:
    vector<T> _Rqueue;
    int _size;       // 数组的容量大小 
    sem_t _ps;       // 生产者的信号量
    sem_t _cs;       // 消费者的信号量
    int _Pindex = 0; // 生产者的下标
    int _Cindex = 0; // 消费者的下标
};

main.cc

#include "RingQueue.hpp"
#include "Task.hpp"

void *Pplay(void *rp)
{
    RingQueue<CPTask> *rq = (RingQueue<CPTask> *)rp;

    while (1)
    {
        string s("+-*/");
        // 随机产生数据插入
        int x = rand() % 100 + 1;
        int y = rand() % 100 + 1;
        // 随机提取+-*/
        int i = rand() % s.size();
        // 定义好实现类的对象
        CPTask c(x, y, s[i], Math);

        // 将整个对象插入到计算队列中
        rq->push(c);
        cout << "生产数据完成: " << c.tostring() << endl;
        sleep(1);
    }
}

void *Cplay(void *cp)
{
    RingQueue<CPTask> *rq = (RingQueue<CPTask> *)cp;

    while (1)
    {
        // 队列拿出数据
        CPTask c;
        rq->pop(&c);
        // 调用拿出的对象获取最终的结果
        string s = c();

        cout << "消费完成,取出的数据为: " << s << endl;
        sleep(2);
    }
}

int main()
{
    srand(time(nullptr));

    RingQueue<CPTask> *rq = new RingQueue<CPTask>();

    pthread_t p, c;
    pthread_create(&p, nullptr, Pplay, (void *)rq);
    pthread_create(&c, nullptr, Cplay, (void *)rq);

    pthread_join(p, nullptr);
    pthread_join(c, nullptr);

    delete rq;

    return 0;
}

效果

image-20230717171143701

对于多生产多消费的情况

如果现在有多个生产和多个消费,那么就要保证临界资源的安全,这时候就得加锁。

加锁可以实现在申请信号量之后,因为申请信号量实际上就是一个原子性的操作,并不会因为多线程而导致冲突。当线程申请好了各自的信号量之后再往下运行加锁,就不用让所有线程都等待在加锁前。而解锁同样可以在发布信号量之后。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/147359.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

spring 整合 JUnit

大家好&#xff0c;本篇博客我们通过spring来整合JUnitt单元测试框架。 在之前篇章的测试方法中&#xff0c;几乎都能看到以下的两行代码&#xff1a; ApplicationContext context new ClassPathXmlApplicationContext("xxx.xml"); Xxxx xxx context.getBean(Xxx…

ctyunos 与 openeuler

ctyunos-2.0.1-220311-aarch64-dvd ctyunos-2.0.1-220329-everything-aarch64-dvd glibc python3 对应openEuler 20.03 LTS SP1

μC/OS-II---互斥信号量管理2(os_mutex.c)

目录 背景&#xff1a;优先级反转问题互斥信号量管理互斥信号量发出&#xff08;释放&#xff09;互斥信号量获取/无等待互斥信号量状态查询 背景&#xff1a;优先级反转问题 在高优先级任务等待低优先级任务释放资源时&#xff0c;第三个中等优先级任务抢占了低优先级任务。阻…

【京东API】商品详情+搜索商品列表接口

利用电商API获取数据的步骤 1.申请API接口&#xff1a;首先要在相应电商平台上注册账号并申请API接口。 2.获取授权&#xff1a;在账号注册成功后&#xff0c;需要获取相应的授权才能访问电商API。 3.调用API&#xff1a;根据电商API提供的请求格式&#xff0c;通过编程实现…

8.GC基本原理

目录 概述垃圾回收引用计数法 (Reference Counting)根可达分析算法 (GCRooting Tracing)对象引用类型强引用软引用弱引用 清除垃圾1.标记-清除算法 (Mark-Sweep)2.复制算法 (Copying)3.标记-整理算法 (Mark-Compact)分代回收 (Generational Collection) 垃圾回收器GC-串行收集器…

力扣每日一题-K个元素的最大和-2023.11.15

力扣每日一题&#xff1a;K个元素的最大和 题目链接:2656.K个元素的最大和 题目描述 代码思路 题目看完直接笑嘻了&#xff0c;还有这么容易的题。由题可知&#xff0c;第一次要找出最大值m&#xff0c;那由于把m1放回去&#xff0c;那第二次找的就是m1&#xff0c;以此类推…

DGL如何表征一张图

有关于DGL中图的构建 DGL 将有向图表示为一个 DGL 图对象。图中的节点编号连续&#xff0c;从0开始。我们一般通过指定图中的节点数&#xff0c;以及源节点和目标节点的列表&#xff0c;来构建这么一个图。 下面的代码构造了一个图&#xff0c;这个图有五个叶子节点。中心节点…

python 多线程池 CPU拉满

前言&#xff1a; 关于多线程的博客先前的博客如下&#xff1a; python线程join方法_python 线程join-CSDN博客 【精选】Python GIL锁_python gil 锁-CSDN博客 python函数运行加速_python os.listdir速度慢_两只蜡笔的小新的博客-CSDN博客 只需下面的模版即可: from multi…

CNCC 2023收官,Milvus Cloud与行业大咖共话向量数据库系统

近期,CNCC 2023 在沈阳圆满结束,紧凑、前沿的 129 场技术论坛让人印象深刻。据悉,这 129 场技术论坛涵盖人工智能、安全、计算+、软件工程、教育、网络、芯片、云计算等 30 余个方向。Zilliz 受邀参与【智能时代的大数据系统】技术论坛。 智能时代的到来,无疑给社会经济和日…

前端 vue 面试题 (一)

文章目录 v-if,v-show差别v-for和v-if虚拟dom解决什么问题vue的data为什么返回函数不返回对象比较vue&#xff0c;reactvue双向绑定原理vue虚拟dom 的diff算法vue 虚拟dom的diff算法的时间复杂度vue2与vue3的区别vue数据缓存&#xff0c;避免重复计算单页应用怎么跨页面传参vue…

基于springboot实现学生选课平台管理系统项目【项目源码】

系统开发平台 在该地方废物回收机构管理系统中&#xff0c;Eclipse能给用户提供更多的方便&#xff0c;其特点一是方便学习&#xff0c;方便快捷&#xff1b;二是有非常大的信息储存量&#xff0c;主要功能是用在对数据库中查询和编程。其功能有比较灵活的数据应用&#xff0c…

队列与堆栈:原理、区别、算法效率和应用场景的探究

队列与堆栈&#xff1a;原理、区别、算法效率和应用场景的探究 前言原理与应用场景队列原理应用场景&#xff1a; 堆栈原理应用场景递归原理和堆栈在其中的作用递归原理堆栈作用 队列与堆栈区别队列堆栈算法效率 前言 本文主要讲解数据结构中队列和堆栈的原理、区别以及相关的…

解析数据洁净之道:BI中数据清理对见解的深远影响

本文由葡萄城技术团队发布。转载请注明出处&#xff1a;葡萄城官网&#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。 前言 随着数字化和信息化进程的不断发展&#xff0c;数据已经成为企业的一项不可或缺的重要资源。然而&#xff0c;这…

0基础学习VR全景平台篇第121篇:认识视频剪辑软件Premiere

上课&#xff01;全体起立~ 大家好&#xff0c;欢迎观看蛙色官方系列全景摄影课程&#xff01; 大家好&#xff0c;这节课是带领大家认识认识我们的剪辑软件Premiere&#xff0c;一般简称是PR。 &#xff08;PR界面&#xff09; 我们首先打开PR&#xff0c;第一步就是要创建…

滚雪球学Java(64):LinkedHashSet原理及实现解析

咦咦咦&#xff0c;各位小可爱&#xff0c;我是你们的好伙伴——bug菌&#xff0c;今天又来给大家普及Java SE相关知识点了&#xff0c;别躲起来啊&#xff0c;听我讲干货还不快点赞&#xff0c;赞多了我就有动力讲得更嗨啦&#xff01;所以呀&#xff0c;养成先点赞后阅读的好…

【数据结构】堆(Heap):堆的实现、堆排序、TOP-K问题

目录 堆的概念及结构 ​编辑 堆的实现 实现堆的接口 堆的初始化 堆的打印 堆的销毁 获取最顶的根数据 交换 堆的插入&#xff08;插入最后&#xff09; 向上调整&#xff08;这次用的是小堆&#xff09; 堆的删除&#xff08;删除根&#xff09; 向下调整&#xff08;这次用的…

dgl 的cuda 版本 环境配置(dgl cuda 版本库无法使用问题解决)

1. 如果你同时有dgl dglcu-XX.XX 那么&#xff0c;应该只会运行dgl &#xff08;DGL的CPU版本&#xff09;&#xff0c;因此&#xff0c;你需要把dgl(CPU)版本给卸载了 但是我只卸载CPU版本还不够&#xff0c;我GPU 版本的dglcu依旧不好使&#xff0c;因此吧GPU版本的也得卸载…

Python武器库开发-flask篇之路由和视图函数(二十二)

flask篇之路由和视图函数(二十二) 通过创建路由并关联函数&#xff0c;实现一个基本的网页&#xff1a; #!/usr/bin/env python3 from flask import Flask# 用当前脚本名称实例化Flask对象&#xff0c;方便flask从该脚本文件中获取需要的内容 app Flask(__name__)#程序实例需…

2.5 Windows驱动开发:DRIVER_OBJECT对象结构

在Windows内核中&#xff0c;每个设备驱动程序都需要一个DRIVER_OBJECT对象&#xff0c;该对象由系统创建并传递给驱动程序的DriverEntry函数。驱动程序使用此对象来注册与设备对象和其他系统对象的交互&#xff0c;并在操作系统需要与驱动程序进行交互时使用此对象。DRIVER_OB…

云服务器如何选?腾讯云2核2G3M云服务器88元一年!

作为一名程序员&#xff0c;在选择云服务器时&#xff0c;我们需要关注几个要点&#xff1a;网络稳定性、价格以及云服务商的规模。这些要素将直接影响到我们的使用体验和成本效益。接下来&#xff0c;我将为大家推荐一款性价比较高的轻应用云服务器。 腾讯云双11活动 腾讯云…