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

news2024/12/23 15:08:08

目录

前言

一、信号量

二、信号量的接口

1.初始化

2.销毁 

3.申请信号量

4. 释放信号量

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

1.环形队列的理解

2.生产者消费者的设计 

3.单消费者单生产者环形队列的实现 

4.多消费者多生产者环形队列的实现


前言

之前,我们学习了线程互斥与线程同步与生产者消费者模型,了解了互斥锁能够很好的保护公共资源,但是之前我们都是将公共资源整体来使用,如果我们可以将公共资源拆分为N份,让线程访问之前先申请信号量,申请到我就将其中一份资源给你,你自己处理,这样操作不会影响到其他线程访问自己的那一块公共资源。提高了效率。

一、信号量

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

申请信号量就是预定资源

信号量的PV(申请、释放)操作是原子的

现在我们不将公共资源当成整体,而是将他拆分为好几份,多线程也不再访问临界资源的同一个区域,而是访问自己申请的那一块区域,就能支持多线程并发访问。

比如现在有5份资源,也就是说最理想的状况,能让5个进程一起访问这五份资源,那么我们只需要控制好,不要让第六个线程来访问资源就好。

那么我们知道,信号量的本质就是计数器,并且是原子的,那么我可以让线程先去申请信号量,来判断是否有你能访问的资源,没有就申请失败,有就让你可以访问这块资源。最后访问完释放资源

同时,当我们申请信号量成功,就证明有一份资源被我预定了,那么我们也无需再次判断该资源是否准备就绪,直接使用即可。

二、信号量的接口

1.初始化

sem_init

  • 作用:初始化信号量
  • 参数1:sem,需要初始化哪个信号量
  • 参数2:pshared,是否需要在进程间共享(0表示线程间共享,非0表示在进程间共享)
  • 参数3:value,定义的初始值,代表最多让几个线程进入。

2.销毁 

sem_destroy

  • 作用:销毁一个信号量
  • 参数:sem,代表要销毁哪个信号量。

3.申请信号量

sem_wait

  • 作用:申请信号量
  • 参数:sem,代表申请哪个信号量。

如果申请失败,会阻塞在wait处。

sem_trywait(非阻塞式申请信号量)

sem_timedwait(根据按照时间进行申请,在时间内申请成功就返回继续执行,超过时间还没申请成功也返回错误代码)

4. 释放信号量

sem_post

  • 作用:释放信号量
  • 参数:sem,释放的是哪个信号量

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

1.环形队列的理解

现在我们使用上面的信号量接口,来写一个基于环形队列的生产者消费者模型。

说是一个环形队列,其实本质上就是一个数组,从头放到尾部,如果当n大于了数组的长度,我们就让n%=v.size(),这样n又会回到索引0的位置。就像环一样滚动起来了。

2.生产者消费者的设计 

既然是生产者消费者模型,我们就得让生产者去生产数据并往环形队列里面放入,消费者从环形队列中拿取数据

如果消费者不进行消费,生产者最多能往队列里面生产 n 份数据。也就是消费者被生产者刚好超了一圈。如下生产者生产了一圈,消费者一直没有消费,此时生产者就不能再生产了,因为这会造成数据覆盖,必须让消费者去消费之后再生产。

同理,消费者也最多赶上生产者,就不能再消费了,因为数据已经被消费者消费完了,生产者还没来得及生产。

那么,生产者和消费者只有两种情况下会在同一个位置,要么为空,要么未满。

为空,应该让生产者去生产,为满,应该让消费者去消费。

但是为空或者为满只是占了实际生产消费情况的少部分,更多的情况并没有那么极端。因此我们只需要偶尔进行维持就可以了,这样就能让生产者和消费者同时进行操作了。

对于生产者来讲,空间是资源,对于消费者来讲,数据是资源

因此我们就需要有两个信号量

  • 生产者需要空间资源(sem_space),默认初始化为N,每次生产者生产,就让N-1,每次消费者消费,就让N+1,如果N为0,代表不能再生产,只能消费。
  • 消费者需要数据资源(sem_data),默认初始化为0,每次生产者生产,都让数据资源+1,消费者消费,就让数据资源-1,如果数据资源为0,代表只能生产,不能消费。

生产者伪代码:

P(sem_space)//申请空间信号量  sem_space - 1

//进行生产

V(sem_data)  //释放数据信号量 sem_data + 1

 消费者伪代码:

P(sem_data)    //申请数据信号量  sem_data - 1

//进行消费

V(sem_space)  //释放空间信号量 sem_space + 1

3.单消费者单生产者环形队列的实现 

代码部分并不难,都是上面的逻辑,不多赘述。

RingQueue.hpp

#pragma once

#include<iostream>
#include<vector>
#include<semaphore.h>
using namespace std;

const static int queue_size = 5;

template<class T>
class RingQueue
{
private:
    void P(sem_t &sem)
    {
        sem_wait(&sem);
    }
    void V(sem_t &sem)
    {
        sem_post(&sem);
    }

public:
    RingQueue(int size = queue_size)
        :_ringqueue(size)
        ,_size(size)
        ,_p_step(0)
        ,_c_step(0)
    {
        sem_init(&_space_sem,0,_size);
        sem_init(&_data_sem,0,0);
    }

    void Push(const T& in)
    {
        P(_space_sem);
        _ringqueue[_p_step] = in;
        _p_step++;
        _p_step %= _size;
        V(_data_sem);
    }

    void Pop(T* out)
    {
        P(_data_sem);
        *out = _ringqueue[_c_step];
        _c_step++;
        _c_step %= _size;
        V(_space_sem);
    }

    ~RingQueue()
    {
        sem_destroy(&_space_sem);
        sem_destroy(&_data_sem);
    }
private:
    vector<T> _ringqueue;
    int _size;

    int _p_step;
    int _c_step;

    sem_t _space_sem;
    sem_t _data_sem;
};

Main.cc 

#include "RingQueue.hpp"
#include <pthread.h>
#include <unistd.h>

void* Productor(void* args)
{
    RingQueue<int>* rq = static_cast<RingQueue<int>*> (args);
    int cnt = 100;
    while(true)
    {
        rq->Push(cnt);
        cout<<"生产成功,数据为: "<<cnt--<<endl;
    }
}

void* Consumer(void* args)
{
    RingQueue<int>* rq = static_cast<RingQueue<int>*> (args);
    while (true)
    {
        sleep(1);
        int data = 0;
        rq->Pop(&data);
        cout<<"消费成功,数据为: "<<data<<endl;
    }
    
}

int main()
{
    pthread_t p,c;
    RingQueue<int> *rq = new RingQueue<int>();
    pthread_create(&c,nullptr,Productor,rq);
    pthread_create(&p,nullptr,Consumer,rq);

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

运行结果如下,由于我们让消费者每隔一秒进行消费,因此打印内容为生产满后,消费一个就生产一个。因为我们维持好了信号量,所以不用担心生产和消费会越阶。

4.多消费者多生产者环形队列的实现

现在我们想让有多个消费者和多个生产者一起去操作。虽然我们确实预先申请了信号量,但是环形队列的实现生产有一个位置,消费有一个位置。(环形队列并没有实现拆分公共资源)

他们必须要保持该位置只有同一时间一个线程进入,因此需要添加两把互斥锁来保证消费者的互斥和生产者的互斥

两把锁的目的是让生产者和消费者一起进行,如果只有一把锁就同一时间只能有一个进行。

我们代码部分也比较简单啊,定义锁,初始化锁,加锁与解锁,销毁锁就可以了。 

同时,我们创建好多个消费者和生产者,让他们运行起来。 

运行发现,成功进行多生产多消费,速度是要比之前学习的阻塞队列快。因为他能让生产者和消费者同时进行访问,阻塞队列只能要么生产者生产,要么消费者消费。 

最后附上总代码

RingQueue.hpp

#pragma once

#include <iostream>
#include <vector>
#include <semaphore.h>
#include <pthread.h>
#include "LockGuard.hpp"
using namespace std;

const static int queue_size = 5;

template <class T>
class RingQueue
{
private:
    void P(sem_t &sem)
    {
        sem_wait(&sem);
    }
    void V(sem_t &sem)
    {
        sem_post(&sem);
    }

public:
    RingQueue(int size = queue_size)
        : _ringqueue(size), _size(size), _p_step(0), _c_step(0)
    {
        sem_init(&_space_sem, 0, _size);
        sem_init(&_data_sem, 0, 0);
        pthread_mutex_init(&_c_mutex, nullptr);
        pthread_mutex_init(&_p_mutex, nullptr);
    }

    void Push(const T &in)
    {
        P(_space_sem);
        // pthread_mutex_lock(&_p_mutex);
        {
            LockGuard(&_p_mutex);
            _ringqueue[_p_step] = in;
            _p_step++;
            _p_step %= _size;
        }
        // pthread_mutex_unlock(&_p_mutex);
        V(_data_sem);
    }

    void Pop(T *out)
    {
        P(_data_sem);
        // pthread_mutex_lock(&_c_mutex);
        {
            LockGuard(&_c_mutex);
            *out = _ringqueue[_c_step];
            _c_step++;
            _c_step %= _size;
        }
        // pthread_mutex_unlock(&_c_mutex);
        V(_space_sem);
    }

    ~RingQueue()
    {
        sem_destroy(&_space_sem);
        sem_destroy(&_data_sem);
        pthread_mutex_destroy(&_p_mutex);
        pthread_mutex_destroy(&_c_mutex);
    }

private:
    vector<T> _ringqueue;
    int _size;

    int _p_step;
    int _c_step;

    sem_t _space_sem;
    sem_t _data_sem;

    pthread_mutex_t _p_mutex;
    pthread_mutex_t _c_mutex;
};

 LockGuard.hpp

#pragma once
#include <pthread.h>

// 不定义锁,外部会传递锁
class Mutex
{
public:
    Mutex(pthread_mutex_t *lock)
        : _lock(lock)
    {
    }
    void Lock()
    {
        pthread_mutex_lock(_lock);
    }
    void UnLock()
    {
        pthread_mutex_unlock(_lock);
    }
    ~Mutex()
    {
    }

private:
    pthread_mutex_t *_lock;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *lock)
        : _mutex(lock)
    {
        _mutex.Lock();
    }
    ~LockGuard()
    {
        _mutex.UnLock();
    }
private:
    Mutex _mutex;
};

Main.cc 

#include "RingQueue.hpp"
#include <pthread.h>
#include <unistd.h>

void* Productor(void* args)
{
    RingQueue<int>* rq = static_cast<RingQueue<int>*> (args);
    int cnt = 100;
    while(true)
    {
        rq->Push(cnt);
        cout<<"生产成功,数据为: "<<cnt--<<endl;
    }
}

void* Consumer(void* args)
{
    RingQueue<int>* rq = static_cast<RingQueue<int>*> (args);
    while (true)
    {
        int data = 0;
        rq->Pop(&data);
        cout<<"消费成功,数据为: "<<data<<endl;
    } 
}

int main()
{
    pthread_t p[2],c[2];
    RingQueue<int> *rq = new RingQueue<int>();
    pthread_create(&p[0],nullptr,Productor,rq);
    pthread_create(&c[0],nullptr,Consumer,rq);
    pthread_create(&p[1],nullptr,Productor,rq);
    pthread_create(&c[1],nullptr,Consumer,rq);
    pthread_join(p[0],nullptr);
    pthread_join(c[0],nullptr);
    pthread_join(p[1],nullptr);
    pthread_join(c[1],nullptr);
}

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

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

相关文章

MGRE中的OSPF配置

一、实验图 二、实验配置 R1 R2 R3 R4 R5 R6

EXCEL中COUNT和COUNTIF的参数类型有什么不同?

来看一下它们的语法&#xff1a; 1.COUNT(值) COUNT函数是计数数字的个数&#xff0c;注意是数值型数字&#xff0c;可不包括文本型数字&#xff0c;它的参数“值”可以是来自单元格的单一区域&#xff0c;如A1:C2&#xff0c;也可以是来自单元格的复合不规则区域&#xff0c…

技术方案应该这么写

简介 新入职一个华为十年工作经验的老Java。让写一个设计方案&#xff0c;其实也不算难&#xff0c;根据业务需要存取三千万数据&#xff0c;三天没写出来&#xff0c;最后做了辞退处理。其实我相信这个老技术员是有能力的&#xff0c;只是没有合适的机会表达。但是也侧面的说明…

9. Spring Boot 日志文件

本篇文章源码位置延续上个章节&#xff1a;SpringBoot_demo 本篇文章内容源码位于上述地址的com/chenshu/springboot_demo/logging包下 1. 日志的作用 发现和定位问题&#xff1a; 日志是程序的重要组成部分&#xff0c;它在系统、程序出现错误或异常时提供诊断和解决问题的线…

学习经验分享【32】本科/硕士开题报告、中期报告等写作经验分享

本科/硕士阶段首先就是要写开题报告&#xff0c;然后中期报告&#xff0c;这篇博文就是分享一下写报告的经验&#xff0c;避免被老师打回来。本人有丰富的写报告经验&#xff0c;有需要的朋友可添加文末联系方式与我联系。 一、本科开题报告的提纲 课题来源及研究的目的和意义…

C++内存分布

C代码编译过程 预处理 宏定义展开、头文件展开、条件编译&#xff0c;这里并不会检查语法编译检查语法&#xff0c;将预处理后文件编译生成汇编文件汇编将汇编文件生成目标文件(二进制文件)链接将目标文件链接为可执行程序 进程的内存分布 程序运行起来(没有结束前)就是一个…

openjudge_2.5基本算法之搜索_166:The Castle

题目 166:The Castle 总时间限制: 1000ms 内存限制: 65536kB 描述 Figure 1 shows the map of a castle.Write a program that calculates how many rooms the castle hashow big the largest room is The castle is divided into m * n (m<50, n<50) square modules.…

如何应对Android面试官 -> startActivity 流程详解

前言 本章主要讲解下 Activity 的启动流程&#xff1b; 整体概念 点击桌面图标&#xff0c;启动目标应用程序的 Activity&#xff0c;首先会跟 AMS 打交道&#xff0c;也就是 SystemServer 进程中启动的AMS&#xff0c;Launcher 进程和 SystemServer 进程中的 AMS 通信「一次跨…

(九)C++自制植物大战僵尸游戏自定义对话框的实现

植物大战僵尸游戏开发教程专栏地址http://t.csdnimg.cn/m0EtD 对话框在游戏的交互中非常重要。在游戏中&#xff0c;对话框不仅可以提醒用户下达任务指令&#xff0c;而且还可以让用户进行操作&#xff0c;自定义游戏中的各种属性。对话框在游戏的交互中非常常见且大量使用。Co…

树莓集团与天府新区信息职业学院在国际数字影像产业园成功举办授牌仪式

2024年4月12日&#xff0c;树莓集团与天府新区信息职业学院共同在国际数字影像产业园举办授牌仪式。这场仪式不仅标志着双方合作的正式开启&#xff0c;更是为未来的产教融合、学生实训与就业推荐树立了坚实的基石。 仪式上&#xff0c;天府新区信息职业学院领导与树莓集团的代…

Qt QStyle详解

1.简介 QStyle类是 Qt 框架中用于控制应用程序界面元素外观的一个抽象基类。这个类提供了一种方式来定制窗口部件&#xff08;widgets&#xff09;的绘制和行为&#xff0c;可以通过改变主题或风格来更改应用程序的外观&#xff0c;而无需修改窗口部件本身的代码。 Qt包含一组…

抽奖系统设计

如何设计一个百万级用户的抽奖系统&#xff1f; - 掘金 如何设计百万人抽奖系统…… 在实现抽奖逻辑时&#xff0c;Redis 提供了多种数据结构&#xff0c;选择哪种数据结构取决于具体的抽奖规则和需求。以下是一些常见场景下推荐使用的Redis数据结构&#xff1a; 无序且唯一奖…

系统边界图

系统边界图的定义&#xff1a; 系统边界图是系统工程和软件工程中的一种图形化工具&#xff0c;用于描述系统与外部世界之间的交互和界限。它展示了系统的组成部分以及这些组件如何与外部实体进行通信和交互。系统边界图通常包括系统内部的各个组件、外部实体以及它们之间的通信…

【Qt】:事件的处理

系统相关 一.鼠标事件二.键盘事件三.定时器 事件是应用程序内部或者外部产生的事情或者动作的统称。在Qt中使用一个对象来表示一个事件。所有的Qt事件均继承于抽象类QEvent。事件是由系统或者Qt平台本身在个同的的刻友出的。当用广投下鼠标、敲下键盘&#xff0c;或者是窗口需要…

掼蛋残局技巧

一、根据对手张数出牌 口诀&#xff1a;十打二来九打一&#xff0c;打成八张不着急&#xff1b; 对手七张或八张&#xff0c;可以反手打一夯&#xff1b; 五打二来六打三&#xff0c;打得对手把眼翻&#xff1b; 枪不打四&#xff1b; 两张&#xff1a;出两张以上的牌&#xff…

AI预测福彩3D第36弹【2024年4月15日预测--第8套算法开始计算第4次测试】

今天咱们继续测试第8套算法和模型&#xff0c;今天是第3次测试&#xff0c;目前的测试只是为了记录和验证&#xff0c;不建议大家盲目跟买。。我的目标仍旧是10次命中3-4次!~废话不多说了&#xff0c;直接上结果&#xff01; 2024年4月15日3D的七码预测结果如下 第一套…

【Leetcode每日一题】 动态规划 - 下降路径最小和(难度⭐⭐)(55)

1. 题目解析 题目链接&#xff1a;931. 下降路径最小和 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了. 2.算法原理 对于这类路径类问题&#xff0c;通常我们首先需要分析状态表示以及状态转移的过程。特别地&#xff0c;本题涉及…

虚良SEOPython脚本寄生虫程序源码

本程序&#xff0c;快速收录百度首页&#xff0c;3-5天就可以有流量&#xff0c;长期稳定&#xff0c;可以设置自动推送。 点这里 Python脚本寄生虫程序源码&#xff08;寄生虫电影脚本&#xff09; - 虚良SEO 模板可以自己修改&#xff0c;源码带模板标签说明&#xff0c;简…

106.从中序与后序遍历构造二叉树

给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 思路&#xff1a; 中序遍历数组中&#xff0c;找到一个根节点&#xff0c;那么其前为其左子树&a…

CameraCtrl、EDTalk、Sketch3D、Diffusion^2、FashionEngine

本文首发于公众号&#xff1a;机器感知 CameraCtrl、EDTalk、Sketch3D、Diffusion^2、FashionEngine NVINS: Robust Visual Inertial Navigation Fused with NeRF-augmented Camera Pose Regressor and Uncertainty Quantification In recent years, Neural Radiance Fields …