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

news2024/9/23 19:27:24

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);

等待信号量

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

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

 发布信号量

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

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

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()变化中,我们可以在外部就找到临界资源你的情况。信号量可以帮我们提前了解资源情况。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

67 自注意力【动手学深度学习v2】

67 自注意力【动手学深度学习v2】 深度学习学习笔记 学习视频&#xff1a;https://www.bilibili.com/video/BV19o4y1m7mo/?spm_id_fromautoNext&vd_source75dce036dc8244310435eaf03de4e330 给定长为n 的序列&#xff0c;每个xi为长为d的向量&#xff0c;自注意力将xi 既当…

Java中的异常处理

1.概述 在 Java 中&#xff0c;所有的异常都有一个共同的祖先java.lang包中的 Throwable类。 异常是程序中的一些错误&#xff0c;但并不是所有的错误都是异常&#xff0c;并且错误有时候是可以避免的。 比如说&#xff0c;你的代码少了一个分号&#xff0c;那么运行出来结果…

二叉树和堆的讲解和实现(图解+代码/C语言)

今天和大家分享的是二叉树的实现&#xff0c;关于遍历二叉树部分均采用递归的方式实现&#xff0c;最后附上部分OJ题供大家练习。 文章目录一、树的概念及结构1.1 树的概念1.2 树的相关概念1.3 树的表示二、二叉树的概念及结构2.1 概念2.2 二叉树的性质2.3 二叉树的存储结构2.…

proxy代理与reflect反射

proxy代理与reflect 在这之前插入一个知识点arguments&#xff0c;每个函数里面都有一个arguments&#xff0c;执行时候不传默认是所有参数&#xff0c;如果传了就是按顺序匹配&#xff0c;箭头函数没有 代理函数 代理对象也就是生成一个替身&#xff0c;然后这个替身处理一切的…

【深度学习】认识神经网络

上一章——过拟合与正则化 从本章开始我们将学习深度学习的内容&#xff1a;包括神经网络和决策树等高级算法 文章目录神经网络的生物学原理神经网络的算法结构案例——图像感知神经网络的前向传播神经网络的生物学原理 在最开始&#xff0c;人们想要构建一个能够模拟生物大脑…

Python __doc__属性:查看文档

在使用 dir() 函数和 __all__ 变量的基础上&#xff0c;虽然我们能知晓指定模块&#xff08;或包&#xff09;中所有可用的成员&#xff08;变量、函数和类&#xff09;&#xff0c;比如&#xff1a;import string print(string.__all__)程序执行结果为&#xff1a;[ascii_lett…

Zabbix 构建监控告警平台(六)

监控TCP连接监控MySQL监控php-fpm监控 Apache监控 MySQL A-B监控磁盘I/O1.监控TCP连接 1.1 tcp状态简介 netstat中的各种状态&#xff1a; CLOSED 初始&#xff08;无连接&#xff09;状态。 LISTEN 侦听状态&#xff0c;等待远程机器的连接…

自动驾驶规控课程学习——决策规划

行为决策系统的规划1 行为决策基础1.1 基本概念与任务行为类型&#xff1a;系统输入输出&#xff1a;输入&#xff1a;定位、感知、地图等输出&#xff1a;决策意图小例子&#xff1a;1.2决策系统的评价与挑战评价指标挑战&#xff08;1&#xff09;决策密度&#xff08;2&…

卡尔曼滤波器与DSP实现

卡尔曼滤波器是利用系统状态方程&#xff0c;结合测量结果对系统状态进行进行最优估计的算法。本文介绍它的主要公式&#xff0c;并举例在C6000 DSP上实现。 推荐资料 KalmanFilter.NETUnderstanding Kalman Filters卡尔曼滤波与组合导航原理 “If you can’t explain it sim…

rust 程序设计语言入门(1)

本文是阅读《Rust程序设计语言》的学习记录&#xff0c;配合视频《Rust编程语言入门教程》食用更佳 环境搭建 windows下载rustup_init.exe&#xff0c;点击安装&#xff0c;默认选择msvc的toolchain&#xff0c;一路default即可 解决下载慢的问题&#xff0c;在powershell中修…

libxlsxwriter条件格式

今天来看一个libxlsxwriter的高级用法&#xff1a;一个条件格式的示例。 说它“高级”&#xff0c;也是基于非Excel专家的小白们的视角。对&#xff0c;没错&#xff0c;本小白正是这样的小白。 1 一个简单的问题 来看我们今天的场景问题&#xff1a;有一列数据&#xff0c;有…

操作系统(一): 进程和线程,进程的多种状态以及进程的调度算法

文章目录前言一、进程和线程1. 进程2. 线程二、进程和线程的区别(面试常问)三、进程调度算法3.1. 批处理系统3.2. 交互式系统3.2.1 时间片轮转3.2.2 优先级调度3.2.3 多级别反馈队列3.3. 实时系统四、进程的状态五、进程同步5.1 什么是进程同步5.2 进程同步应该遵循的几点原则前…

Qt 学习(四) —— QGridLayout栅格布局

目录一、QGridLayout布局规则二、创建QGridLayout三、成员函数1. 控件间距2. 可拉伸控件&#xff08;弹簧&#xff09;3. 最小行高/列宽4. 行数和列数5. 锁定纵横比6. 添加控件7. 添加布局8. 设置栅格布局原点位置9. 操作布局项9.1 访问布局项9.2 删除布局项9.3 通过索引获取布…

Git教程个人分享:如何将一个本地项目上传至远程仓库的流程

前言&#xff1a; 今天来分享一下&#xff0c;关于Git的一些教程&#xff0c;同时这也是我自己曾今学习Git时候的笔记&#xff0c;之所以更&#xff0c;也是方便后期自己可以去回顾&#xff0c;当然后面也会出一部分关于Git其他操作方面的内容。 这次我们分享的是&#xff0c…

基于JavaScript的Web端股票价格查看器——大道

&#x1f436; 基于JavaScript的Web端股票价格查看器——大道 一、项目背景 当下互联网发展迅速&#xff0c;互联网已经不断向传统金融领域渗透。在互联网上有大量金融领域的数据&#xff0c;如何利用好这些数据&#xff0c;对于投资者来说是十分重要的一件事情。股票价格实时…

JavaSE学习day4_01 循环for,while,do...while

1. 循环高级 1.1 无限循环 for、while、do...while都有无限循环的写法。 最为常用的是while格式的。 因为无限循环是不知道循环次数的&#xff0c;所以用while格式的 代码示例&#xff1a; while(true){} 1.2 跳转控制语句&#xff08;掌握&#xff09; 跳转控制语句&…

MySQL 插入数据

数据库与表创建成功以后&#xff0c;需要向数据库的表中插入数据。在 MySQL 中可以使用 INSERT 语句向数据库已有的表中插入一行或者多行元组数据。 你可以通过 mysql> 命令提示窗口中向数据表中插入数据&#xff0c;或者通过PHP脚本来插入数据。 语法 以下为向MySQL数据表…

51单片机——步进电机实验,小白讲解,相互学习

步进电机简介&#xff1a; 步进电机是将电脉冲信号转变为角位移或多线位移的开源控制元件。在非超载的情况下&#xff0c;电机的转速&#xff0c;停止的位置只取决于脉冲信号的频率和脉冲数&#xff0c;而不受负载变化的影响&#xff0c;即给电机加一个脉冲信号&#xff0c;电机…

Android - 自动系统签名

一、系统签名 以下是两类应用开发场景&#xff1a; 普通应用开发&#xff1a;使用公司自定义 keystore 进行签名&#xff0c;如&#xff1a;微信、支付宝系统应用开发&#xff1a;使用 AOSP 系统签名或厂商自定义 keystore 进行签名&#xff0c;如&#xff1a;设置、录音 系…

数学建模拓展内容:卡方检验和Fisher精确性检验(附有SPSS使用步骤)

卡方检验和Fisher精确性检验卡方拟合度检验卡方独立性检验卡方检验的前提假设Fisher精确性检验卡方拟合度检验 卡方拟合度检验概要&#xff1a;卡方拟合度检验也被称为单因素卡方检验&#xff0c;用于检验一个分类变量的预期频率和观察到的频率之间是否存在显著差异。 卡方拟…