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;}elsecount = 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.hqwc.cn/news/176645.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32——端口复用与重映射概述与配置(HAL库)

文章目录 前言一、什么是端口复用&#xff1f;什么是重映射&#xff1f;有什么区别&#xff1f;二、端口复用配置 前言 本篇文章介绍了在单片机开发过程中使用的端口复用与重映射。做自我学习的简单总结&#xff0c;不做权威使用&#xff0c;参考资料为正点原子STM32F1系列精英…

(免费领源码)python#django#mysql公交线路查询系统85021- 计算机毕业设计项目选题推荐

摘 要 本论文主要论述了如何使用django框架开发一个公交线路查询系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论述该系统的当前背景以及系统开发的目的&#xff0c;后续章节将严格按…

LeetCode(15)分发糖果【数组/字符串】【困难】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 135. 分发糖果 1.题目 n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。 你需要按照以下要求&#xff0c;给这些孩子分发糖果&#xff1a; 每个孩子至少分配到 1 个糖果。相邻两个孩子评分更高的孩子会获…

高级着色语言(HLSL)

High-Level Shading Language&#xff0c;简称为HLSL&#xff0c;可以使用HLSL编写顶点着色器和像素着色器程序&#xff0c;简要地说&#xff0c;顶点着色器和像素着色器就是我们自行编写的一些规模较小的定制程序&#xff0c;这些定制程序可取代固定功能流水线中某一功能模块&…

“技能兴鲁”职业技能大赛-网络安全赛项-学生组初赛 WP

Crypto BabyRSA 共模攻击 题目附件&#xff1a; from gmpy2 import * from Crypto.Util.number import *flag flag{I\m not gonna tell you the FLAG} # 这个肯定不是FLAG了&#xff0c;不要交这个咯p getPrime(2048) q getPrime(2048) m1 bytes_to_long(bytes(flag.e…

数据结构:树的基本概念(二叉树,定义性质,存储结构)

目录 1.树1.基本概念1.空树2.非空树 2.基本术语1.结点之间的关系描述2.结点、树的属性描述3.有序树、无序树4.森林 3.树的常考性质 2.二叉树1.基本概念2.特殊二叉树1.满二叉树2.完全二叉树3.二叉排序树4.平衡二叉树 3.常考性质4.二叉树的存储结构1.顺序存储2.链式存储 1.树 1.…

应用在唱放一体音响麦克风中的投影K歌模组

从十年前智能手机和移动互联网开始兴起&#xff0c;手机K歌&#xff08;全民K歌、唱吧等&#xff09;娱乐潮流成为年轻人的新宠&#xff0c;衍生出全新的手机K歌麦克风。到如今家庭智能电视、智能娱乐影院设备普及&#xff0c;家庭、聚会、户外K歌新娱乐潮流兴起&#xff0c;拓…

智能电网线路阻抗模拟的工作原理

智能电网线路阻抗模拟是一种通过模拟电网线路的阻抗特性来实现电网故障检测和定位的技术。智能电网系统通过安装在电网线路上的传感器&#xff0c;实时采集线路上的电流、电压等参数&#xff0c;并将这些数据传输到监控中心。监控中心接收到传感器采集的数据后&#xff0c;对数…

基于JAVA SpringBoot和HTML美食网站博客程序设计

摘要 美食网站是一个提供各种美食信息和食谱的网站&#xff0c;旨在帮助用户发现、学习和分享美食。旨在探讨美食网站在现代社会中的重要性和影响。随着互联网的普及&#xff0c;越来越多的人开始使用美食网站来获取各种美食信息和食谱。这些网站不仅提供了方便快捷的搜索功能&…

小型洗衣机哪个牌子质量好?性价比高的迷你洗衣机推荐

这两年内衣洗衣机可以称得上较火的小电器&#xff0c;小小的身躯却有大大的能力&#xff0c;一键可以同时启动洗、漂、脱三种全自动为一体化功能&#xff0c;在多功能和性能的提升上&#xff0c;还可以解放我们双手的同时将衣物给清洗干净&#xff0c;让越来越多小伙伴选择一款…

计算机网络期末复习-Part5

1、CRC计算 看例题&#xff1a;待发送序列为101110&#xff0c;生成多项式为X31&#xff0c;计算CRC校验码 先在待发送序列末尾添加与生成多项式次数相同的零&#xff0c;在上述例子中&#xff0c;生成多项式是X^3 1&#xff0c;所以需要添加3个零&#xff0c;待发送序列变成…