【lesson57】信号量和生产者消费者模型(环形队列版)

文章目录

  • 信号量概念
  • 信号量接口
    • 初始化
    • 销毁
    • 等待
    • 发布
  • 基于环形队列的生产者消费者模型
  • 编码
    • Common.h
    • LockGuard.hpp
    • Task.hpp
    • sem.hpp
    • RingQueue.hpp
    • ConProd.cc

信号量概念

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

什么是信号量?
共享资源—>保证任何时刻都只有一个执行流进行访问----->也就有了之前的临界资源,临界区的概念

之前的互斥加锁—>将共享资源当做整个使用。
但是不同的线程可能访问同一个共享资源的不同区域,这样如何我们加个整体锁必定带来整个进行效率的降低。

如果一个共享资源不当做一个整体,而让不同的执行流访问不同的区域的话,那么不就提高了并发度吗?是的。
而只有当不同的线程访问同一个区域的时候我们再进行同步与互斥。

两个问题
1.我们怎么知道一整个共享区,一共有多少个资源,还剩多少个资源?
2.我们怎么保证这个资源就是给我们的?(程序员编码保证)我们怎么知道线程一定可以具有一个共享资源(信号量保证)

电影院买票的例子:
买票的本质:叫做资源(电影院座位)的预订机制

信号量本质:是一个计数器,访问临界资源的时候,必须先申请资源(sem–预订资源),使用完毕后信号量资源必须释放(sem++)
如何理解信号量的使用—>我们申请一个信号量---->当前执行流一定具有一个资源,可以被它使用—>是哪个资源呢?---->需要程序员结合场景,自定义编码完成。

信号量接口

初始化

int sem_init(sem_t *sem, int pshared, unsigned int value);
//参数:
//pshared:0表示线程间共享,非零表示进程间共享
//value:信号量初始值

销毁

 int sem_destroy(sem_t *sem);

等待

//功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem);

发布

//功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);

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

在这里插入图片描述
环形队列采用数组模拟,用模运算来模拟环状特性环形结构起始状态和结束状态都是一样的不好判断为空或者为满,所以可以通过加计数器或者标记位来判断满或者空。另外也可以预留一个空的位置,作为满的状态
但是我们现在有信号量这个计数器,就很简单的进行多线程间的同步过程
在这里插入图片描述
什么时候生产者和消费者会访问同一个位置?
在这里插入图片描述
1.环形队列空的时候,生产者和消费者会访问同一个位置
2.环形队列满的时候,生产者和消费者会访问同一个位置
3.其它情况不会访问同一个位置。

那么该如何解决?
1.用计数器解决
2.专门浪费一个格子解决

如果生产者和消费者指向了环形结构的同一个位置;那么生产者和消费者要有互斥或者同步的问题,不然生产者和消费者指向的都是不同的位置。

当生产者和消费者指向同一个位置,具有互斥同步关系就可以。
当生产者和消费者不指向同一个位置想让他们并发执行呢?
期望:
1.生产者不能将消费者套圈。
2.消费者超过生产者
3.环形队列为空:一定要让生产者先行
4.环形队列为满:一定要让消费者先行

怎么编码保证?
生产者:最关注的是空间资源
消费者:最关注的是空间里面的数据资源
最开始计数器值:
生产者:spaceSem---->为最大的N
消费者:dataSem---->为0

生产者:生产一个数据
P(spaceSem)—>spaceSem- -
V(dataSem)----->dataSem++
消费者:消费一个数据
P(dataSem)—>dataSem- -
V(spaceSem)----->spaceSem++

编码

Common.h

#pragma once
#include <iostream>
#include <array>
#include <functional>
#include <ctime>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>



#define RingQueueNum 5//环形队列空间大小
#define CONSUMRT_NUM 2//生产者个数
#define PRODUCTER_NUM 2//消费者个数
//生产者和消费者个数可以自定义

LockGuard.hpp

#pragma once
#include "Common.h"

//自己封装的锁,可以自动初始化和销毁
class Mutex
{
public:
    Mutex()
    {
        pthread_mutex_init(&_mtx,nullptr);
    }

    pthread_mutex_t& GetMutex()
    {
        return _mtx;
    }

    ~Mutex()
    {
        pthread_mutex_destroy(&_mtx);
    }
private:
    pthread_mutex_t _mtx;
};

//自己封装RAII风格的lock和unlock,会自动解锁解锁
class LockGuard
{
public:
    LockGuard(Mutex* mtx)
    :_mtx(mtx)
    {
        pthread_mutex_lock(&_mtx->GetMutex());
        //std::cout << "lock" << std::endl;
    }


    ~LockGuard()
    {
        pthread_mutex_unlock(&_mtx->GetMutex());
        //std::cout << "unlock" << std::endl;
    }
private:
    Mutex* _mtx;
};

Task.hpp

#pragma once
#include "Common.h"

typedef std::function<int(int, int)> func_t;

//自己写的任务
class Task
{
public:
    Task()
    {}
    
    Task(int x, int y, func_t func)
        : _x(x),
          _y(y),
          _func(func)
    {}

    int operator()()
    {
        return _func(_x, _y);
    }

public:
    int _x;
    int _y;
    func_t _func;
};

sem.hpp

#pragma once
#include "Common.h"

//自己封装一个信号量P(申请信号量),V(释放信号量)
class Sem
{
public:
    Sem(int value = RingQueueNum)
    {
        sem_init(&_sem,0,value);
    }

    void P()
    {
        sem_wait(&_sem);
    }

    void V()
    {
        sem_post(&_sem);
    }

    ~Sem()
    {
        sem_destroy(&_sem);
    }
private:
    sem_t _sem;
};

RingQueue.hpp

#pragma once
#include "Common.h"
#include "LockGuard.hpp"
#include "Sem.hpp"

template<class T>
class RingQueue
{
public:
    RingQueue()
    :_space_sem(RingQueueNum),
     _data_sem(0)
    {}
    void Push(T& in)
    {
    	//先申请信号量
        _space_sem.P();
        LockGuard lock(&_producter_mtx);
        _rq[product_index++] = in;
        product_index %= RingQueueNum;
        _data_sem.V();
    }
    
    void Pop(T* out)
    {
    	//先申请信号量
        _data_sem.P();
        LockGuard lock(&_cosumer_mtx);
        *out = _rq[consumer_index++];
        consumer_index %= RingQueueNum;
        _space_sem.V();
    }
private:
    std::array<T,RingQueueNum> _rq;//环形队列,本质是数组
    int consumer_index = 0;//消费者数组下标
    int product_index = 0;//生产者数组下标
    Mutex _cosumer_mtx;//消费者锁
    Mutex _producter_mtx;//生产者锁

    Sem  _space_sem;//空间资源信号量
    Sem  _data_sem;//数据资源信号量
};

ConProd.cc

#include "Common.h"
#include "RingQueue.hpp"
#include "Task.hpp"

int myadd(int x, int y)
{
    return x + y;
}
void *ConsumerRoutine(void *args)
{
    RingQueue<Task> *rq = (RingQueue<Task> *)args;
    while (true)
    {
    	//获取任务
        Task t;
        rq->Pop(&t);
		
		//执行任务
        std::cout << pthread_self() << "|Get a Task: " << t._x << " + " << t._y << "=" << t() << std::endl;
        sleep(1);
    }
}
void *ProducterRoutine(void *args)
{
    RingQueue<Task> *rq = (RingQueue<Task> *)args;

    while (true)
    {
    	//制作任务
        int x = rand() % 100 + 1;
        int y = rand() % 100 + 1;
        Task t(x, y, myadd);
        std::cout << pthread_self() << "|Make a Task: " << x << " + " << y << "=?" << std::endl;
        //发送任务
        rq->Push(t);
        sleep(1);
    }
}
int main()
{
    srand((unsigned int)time(nullptr) ^ getpid() ^ 0x77777);
	
	//定义线程
    pthread_t consumer[CONSUMRT_NUM];
    pthread_t producter[PRODUCTER_NUM];
	
	//定义环形队列
    RingQueue<Task> *ring_queue = new RingQueue<Task>;

	//创建消费者线程
    for (int i = 0; i < CONSUMRT_NUM; i++)
    {
        pthread_create(consumer + i, nullptr, ConsumerRoutine, (void *)ring_queue);
    }
	
	//创建生产者线程
    for (int i = 0; i < PRODUCTER_NUM; i++)
    {
        pthread_create(producter + i, nullptr, ProducterRoutine, (void *)ring_queue);
    }
	
	//等待消费者线程
    for (int i = 0; i < CONSUMRT_NUM; i++)
    {
        pthread_join(consumer[i], nullptr);
    }

	//等待生产者线程
    for (int i = 0; i < PRODUCTER_NUM; i++)
    {
        pthread_join(producter[i], nullptr);
    }

    return 0;
}

多生产和多消费的意义在哪?
提高线程的并发度。
信号量的本质是一个计数器—>计数器的意义是什么?
以前:申请锁—>判断与访问---->释放锁—>本质我们是不清楚临界资源的情况
现在:信号量要提前预定资源的情况,而在PV变化过程中,我们可以在外部就能知晓临界资源的情况。

计数器的意义:可以不用进入临界区,就可以得知临界资源的情况,甚至可以减少内部资源的判断。

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

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

相关文章

漫漫数学之旅022

文章目录 经典格言数学习题古今评注名人小传- 刘易斯卡罗尔 经典格言 艾丽斯笑着说&#xff1a;“去尝试也毫无用处&#xff0c;一个人无法相信不可能的事情。”——刘易斯卡罗尔&#xff08;Lewis Carroll&#xff09;《艾丽斯梦游仙境&#xff08;Alice in Wonderland&#…

零基础怎么学编程,免费版中文编程工具下载及构件用法教程

零基础怎么学编程&#xff0c;免费版中文编程工具下载及构件用法教程 一、前言 今天给大家分享的中文编程开发语言工具资料如下&#xff1a; 编程入门视频教程链接 http://​ https://edu.csdn.net/course/detail/39036 ​ 编程工具及实例源码文件下载可以点击最下方官网…

基于Python实现Midjourney集成到(个人/公司)平台中

目前Midjourney没有对外开放Api&#xff0c;想体验他们的服务只能在discord中进入他们的频道进行体验或者把他们的机器人拉入自己创建的服务器中&#xff1b;而且现在免费的也用不了了&#xff0c;想使用就得订阅。本教程使用midjourney-api这个开源项目&#xff0c;搭建Midjou…

Linux第55步_根文件系统第2步_测试使用busybox生成的根文件系统

测试使用busybox生成的根文件系统。测试内容较多&#xff0c;很杂。 1、修改“nfs-kernel-server” 1)、打开终端 输入“sudo vi /etc/default/nfs-kernel-server回车”&#xff0c;打开“nfs-kernel-server”文件。 输入密码“123456回车” 见下图&#xff1a; 2)、在最后…

【学网攻】 第(28)节 -- OSPF虚链路

系列文章目录 目录 系列文章目录 文章目录 前言 一、什么是OSPF虚链路&#xff1f; 二、实验 1.引入 实验目标 实验背景 技术原理 实验步骤 实验设备 实验拓扑图 实验配置 扩展 实验拓扑图 实验配置 实验验证 文章目录 【学网攻】 第(1)节 -- 认识网络【学网攻…

模型 4S(满意、服务、速度、诚意)理论

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_总纲目录。重在提升认知。以客户为中心。 1 4S(满意、服务、速度、诚意)理论的应用 1.1 4S 理论在制造业中的应用 某汽车制造企业 A 一直致力于提供高品质的汽车产品和优质的服务&#xff0c;以满足客户的需求和期…

2022年12月电子学会青少年软件编程 中小学生Python编程等级考试二级真题解析(选择题)

2022年12月Python编程等级考试二级真题解析 选择题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 1、运行下列程序&#xff0c;最终输出的结果是 info {1:小明,2:小黄,3:小兰} info[4]小红 info[2]小白 print(info) A、{1:小明,2:小白,3:小红,4:小…

高德地图上绘制热力图的方法

百度地图和高德地图的JavaScript API都提供了热力图的绘制方法&#xff0c;都是将热力图作为新的图层&#xff0c;叠加到地图上。但是百度地图的经纬度体系与我们的经纬度存在偏差&#xff0c;高德的与我们相符&#xff0c;应当使用高德地图JavaScript API。 因为是JavaScript…

最长连续手牌 - 华为OD统一考试

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 200分 题解&#xff1a; Java / Python / C 题目描述 有这么一款单人卡牌游戏&#xff0c;牌面由颜色和数字组成&#xff0c;颜色为红、黄、蓝、绿中的一种&#xff0c;数字为 0−9 中的一个。游戏开始时玩家从手牌中…

【题解】差分

差分其实就是前缀和的逆运算。 如果数组 A 是数组 B 的前缀和数组&#xff0c;则称 B 是 A 的差分数组。 思路 由题意得&#xff0c;应该求给定数组的差分数组。 差分加速的原理 对 L 到 R 区间内的数加上 c&#xff0c;时间复杂度是O(c) &#xff0c;即O(n) 。 但是如果…

SORA:OpenAI最新文本驱动视频生成大模型技术报告解读

Video generation models as world simulators&#xff1a;作为世界模拟器的视频生成模型 1、概览2、Turning visual data into patches&#xff1a;将视觉数据转换为补丁3、Video compression network&#xff1a;视频压缩网络4、Spacetime Latent Patches&#xff1a;时空潜在…

NetMizer 日志管理系统 多处前台RCE漏洞复现

0x01 产品简介 NetMizer是提供集成应用交付和应用安全解决方案以实现业务智能网络的优秀全球供应商,为全球企业和运营商提供确保关键业务应用的全面可用性、高性能和完善的安全性的解决方案。 0x02 漏洞概述 NetMizer 日志管理系统position.php、hostdelay.php、等接口处存在…

leetcode刷题--贪心算法

七. 贪心算法 文章目录 七. 贪心算法1. 605 种花问题2. 121 买卖股票的最佳时机3. 561 数组拆分4. 455 分发饼干5. 575 分糖果6. 135 分发糖果7. 409 最长回文串8. 621 任务调度器9. 179 最大数10. 56 合并区间11. 57 插入区间13. 452 用最少数量的箭引爆气球14. 435 无重叠区间…

鸿蒙系统优缺点,能否作为开发者选择

凡是都有对立面&#xff0c;就直接说说鸿蒙的优缺点吧。 鸿蒙的缺点&#xff1a; 鸿蒙是从2019年开始做出来的&#xff0c;那时候是套壳Android大家都知晓。从而导致大家不看鸿蒙系统&#xff0c;套壳Android就是多次一举。现在鸿蒙星河版已经是纯血鸿蒙&#xff0c;但是它的…

树莓派5 EEPROM引导加载程序恢复镜像

树莓派5不能正常启动&#xff0c;可以通过电源led灯的闪码来判断错误发生的大致情形。 LED警告闪码 如果树莓派由于某种原因无法启动&#xff0c;或者不得不关闭&#xff0c;在许多情况下&#xff0c;LED会闪烁特定的次数来指示发生了什么。LED会闪烁几次长闪烁&#xff0c;然…

02 c++入门

目录 c关键字命名空间c输入&输出缺省参数函数重载引用内联函数auto关键字(c11)基于范围的for循环(c11)指针空值—nullptr(c11) 0. 本节知识点安排目的 c是在c的基础上&#xff0c;容纳进去了面向对象编程思想&#xff0c;并增加了许多有用的库&#xff0c;以及编程范式等…

【HTML】过年不能放烟花,那就放电子烟花

闲谈 大家回家过年可能都多多少少放过些&#x1f9e8;&#xff0c;但是有些在城市上过年的小伙伴可能就没有机会放鞭炮了。不过没关系&#xff0c;我们懂技术&#xff0c;我们用技术自娱自乐&#xff0c;放电子烟花&#xff0c;总不可能被警长叔叔敲门问候吧。 开干 首先&…

【Linux】初步使用makefile

makefile 1 快速使用1.1 认识makefile1.2 使用makefile 2 深入理解理解 **依赖关系 与 依赖方法**如何实现源代码修改了才会重新编译 3 内置符号理解Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;&#xff01;&#xff01;下一篇文章见&#xff01;&a…

C语言程序设计(第四版)—习题7程序设计题

目录 1.选择法排序。 2.求一批整数中出现最多的数字。 3.判断上三角矩阵。 4.求矩阵各行元素之和。 5.求鞍点。 6.统计大写辅音字母。 7.字符串替换。 8.字符串转换成十进制整数。 1.选择法排序。 输入一个正整数n&#xff08;1&#xff1c;n≤10&#xff09;&#xf…

【学习心得】Python好库推荐——captcha

Captcha的全称是"Completely Automated Public Turing test to tell Computers and Humans Apart",完全自动化的图灵测试,用于区分计算机和人类。说直白点就是验证码&#xff0c;验证你是人而不是爬虫。 Captcha的原理就是利用计算机目前还无法进行实时视觉辨识和字符…