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

news2024/11/18 11:48:40

文章目录

  • 信号量
    • 信号量的接口
      • 初始化
      • 销毁
      • 等待信号量
      • 发布信号量
  • 环形队列
    • 结合信号量设计模型
  • 实现基于环形队列的生产者消费者模型
    • 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

对于多生产多消费的情况

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

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

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

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

相关文章

关于修改了mysql的my_conf文件之后,不能生效问题

个人名片&#xff1a; &#x1f405;作者简介&#xff1a;一名大三在校生&#xff0c;热爱生活&#xff0c;爱好敲码&#xff01; \ &#x1f485;个人主页 &#x1f947;&#xff1a;holy-wangle ➡系列内容&#xff1a; &#x1f5bc;️ tkinter前端窗口界面创建与优化 &…

相关关系与因果关系

本文来自&#xff1a;https://towardsdatascience.com/a-step-by-step-guide-in-detecting-causal-relationships-using-bayesian-structure-learning-in-python-c20c6b31cee5 作者&#xff1a;Erdogan Taskesen 在机器学任务中&#xff0c;确定变量间的因果关系&#xff08;c…

CentOS修改root用户密码

一、适用场景 1、太久没有登录CentOS系统&#xff0c;忘记管理密码。 2、曾经备份的虚拟化OVA或OVF模板&#xff0c;使用模板部署新系统后&#xff0c;忘记root密码。 3、被恶意攻击修改root密码后的紧急修复。 二、实验环境 1、VMware虚拟化的ESXI6.7下&#xff0c;通过曾经…

基于PyTorch搭建你的生成对抗性网络

前言 你听说过GANs吗&#xff1f;还是你才刚刚开始学&#xff1f;GANs是2014年由蒙特利尔大学的学生 Ian Goodfellow 博士首次提出的。GANs最常见的例子是生成图像。有一个网站包含了不存在的人的面孔&#xff0c;便是一个常见的GANs应用示例。也是我们将要在本文中进行分享的…

11.3SpringMVC

一.概念 1.SpringMvc: a.构建在Servlet(api)基础上. b.是一个Web框架(HTTP). c.来自于Spring webMVC模块. 2.MVC 二.注册路由的注解 1.RequestMapping("/test") // 路由注册 注意: 这个注解在类和方法上都要使用,代表不同等级的路由. 2.RestController a)R…

FPGA_边沿检测电路设计

FPGA_边沿检测电路设计 边沿检测原理图波形图分析实现方法方法一&#xff1a;与逻辑实现方法二&#xff1a;或逻辑实现方法三&#xff1a;与逻辑实现 边沿检测原理图 由状态转移表可以看出&#xff0c;其转换条件中需要检测到下降沿以及上升沿&#xff0c;而边沿检测其原理就是…

「题解」相交链表

&#x1f349;题目 题目链接 &#x1f349;解析 “提示”部分有提示链表数不为零&#xff0c;所以讨论链表为空的情况。 最简单粗暴的思路就是&#xff1a;遍历链表&#xff0c;先使用循环遍历A链表&#xff0c;然后嵌套循环遍历B&#xff0c;比对A、B是否存在地址相同的…

Windows10配置深度学习环境

一、Anaconda安装与虚拟环境创建 Anaconda的出现极大的方便了研究人员的Python开发工作&#xff0c;anaconda可以创建多个虚拟环境&#xff0c;这些虚拟环境就像一间间教室一样&#xff0c;虚拟环境彼此之间、虚拟环境与基础环境&#xff08;base&#xff09;之间互不影响&…

C++之Max

背景 想学习数据结构&#xff0c;这是看的课程的习题&#xff0c;讲课老师用的是类C语言&#xff0c;具体的实现还是得自己来。 准备工作 用开发工具Microsoft Visual Studio(VS)建立一个空白的C控制台项目 选择项目的存储路径 成功建立 我在 Microsoft Visual Studio中用…

VSCode配置msvc编译调试环境

1.VS Code简介 VS Code 使用 Electron 框架构建用户界面,该框架使用 Chromium 和 Node.js 构建桌面应用程序。这使得 VS Code 能够在 Windows、Linux 和 macOS 上运行,并且可以使用 Web 技术 (HTML、CSS 和 JavaScript) 构建用户界面。 VS Code 使用 Monaco 引擎来提供文本编辑…

OpenCV入门——图像视频的加载与展示一些API

文章目录 OpenCV创建显示窗口OpenCV加载显示图片OpenCV保存文件利用OpenCV从摄像头采集视频从多媒体文件中读取视频帧将视频数据录制成多媒体文件OpenCV控制鼠标关于[np.uint8](https://stackoverflow.com/questions/68387192/what-is-np-uint8) OpenCV中的TrackBar控件TrackBa…

antd中的form表单数据不更新

antd中的form表单 initialValue导致数据不更新问题 理解 &#xff1a; initialValue就是所谓的defaultValue,只会在第一次赋值的时候改变&#xff0c;却又有一些不同&#xff0c;因为 initialValue又会因其他改动而改变。 解决&#xff1a; form.resetFields();

内网Jenkins 部署.net(dotnet)项目

一、前置条件 内网部署Jenkins&#xff0c;并安装好所需插件 此篇内容需承接内网搭建Jenkins自动化远程部署项目到Windows服务器_jenkins内网安装-CSDN博客 &#xff0c;才更好操作与理解 二、在Jenkins中创建项目 三、配置项目 General Source Code Management Build Envi…

2023最新版本 从零基础入门C++与QT(学习笔记) -4- C/C++混合编程

&#x1f38f;在C兼容C只需要调用C头文件 &#x1f384;格式 &#x1f388; -1- extern(关键字) “C”{ }(花括号) &#x1f388; -2- 花括号里面填写要包含的头文件 &#x1f384;代码段格式 extern "C" {#include “stdio.h”#include “string.h” }&#x…

2023深圳高交会,11月15-19日在深圳会展中心盛大开幕,展出五天时间

跨越山海&#xff0c;共赴鹏城。 11月15日至19日&#xff0c;第二十五届中国国际高新技术成果交易会在深圳会展中心&#xff08;福田展区&#xff09;和深圳国际会展中心&#xff08;宝安展区&#xff09;两馆同时举行。一场不可错过的全球性高科技盛会如期而至。 科技赋能发…

2023年数据泄露事件频发,企业应该如何做好数据安全保护?

在当今数字化时代&#xff0c;每时每刻都会产生大量的数据&#xff0c;这些数据包含大量的敏感信息&#xff0c;网络犯罪者可以利用这些数据获取巨大的利益&#xff0c;由此他们会通过技术攻击、网络钓鱼等各种非法手段来获取&#xff0c;而这也导致数据泄露事件频频发生。 以下…

冰点还原精灵Deep Freeze for mac版

Deep Freeze是一种系统恢复软件&#xff0c;它可以保护计算机系统免受恶意软件和不必要的更改。它的基本功能是在计算机重启后恢复到原始状态&#xff0c;即使用户进行了任何更改也不例外。 Deep Freeze主要用于公共场所的计算机&#xff0c;如图书馆、学校实验室和互联网咖啡馆…

JavaScript基础入门05

目录 1.操作结点 1.1新增节点 1.1.1. 创建元素节点 1.1.2. 插入节点到 dom 树中 1.2删除节点 2.代码案例: 猜数字 2.1预期效果 2.2代码实现 3.代码案例: 表白墙 3.1预期效果 3.2创建页面布局 3.3调整样式 3.4实现提交 3.5实现点击按钮的动画效果 4.代码案例: 待办…

小波神经网络的时间序列预测——短时交通流量预测

大家好&#xff0c;我是带我去滑雪&#xff01; 小波神经网络&#xff08;Wavelet Neural Network&#xff0c;WNN&#xff09;结合了小波变换和神经网络的特性&#xff0c;是一种在信号处理和模式识别领域应用广泛的神经网络模型。它的设计灵感来自于小波变换的多尺度分析特性…

Hive 查询优化

Hive 查询优化 -- 本地 set mapreduce.framework.namelocal; set hive.exec.mode.local.autotrue; set mapperd.job.trackerlocal; -- yarn set mapreduce.framework.nameyarn; set hive.exec.mode.local.autofalse; set mapperd.job.trackeryarn-- 向量模式 set hive.vectori…