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

目录

前言

一、信号量

二、信号量的接口

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

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

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

相关文章

AI的说服力如人类?Anthropic最新研究揭秘机器的辩论能力|TodayAI

人们常常对人工智能模型在对话中的说服力表现持怀疑态度。长久以来&#xff0c;社会上一直存在一个疑问&#xff1a;人工智能是否会达到人类那样&#xff0c;在对话中具有改变他人想法的能力&#xff1f; 直到最近&#xff0c;这一领域的实证研究相对有限&#xff0c;对于人工…

HTML5 新增语义标签及属性

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 ✍HTML5 新增语义标签及属性&#x1f48e;1 HTML5 新增的块级语义化标签&…

海外代理IP在跨境电商中发挥什么作用?

在我国跨境电商的发展中&#xff0c;海外代理IP的应用日益广泛&#xff0c;它不仅帮助商家成功打入国际市场&#xff0c;还为他们在多变的全球电商竞争中保持优势。下面是海外代理IP在跨境电商中五个关键的应用场景。 1、精准的市场分析 了解目标市场的消费者行为、产品趋势以…

pyinstaller工具打包python项目详细教程

使用 Pyinstaller工具 编译打包 Python 项目生成 exe 可执行文件 1.pyinstaller介绍&#xff1a; 介绍&#xff1a;PyInstaller 是一个将 Python 程序转换为独立可执行文件的工具。它能够在 Windows、Linux、Mac OS X、AIX 和 Solaris 等多种系统上运行。详细介绍可参考pyins…

记录--病理切片图像处理

简介 数字病理切片&#xff0c;也称为全幻灯片成像&#xff08;Whole Slide Imaging&#xff0c;WSI&#xff09;或数字切片扫描&#xff0c;是将传统的玻片病理切片通过高分辨率扫描仪转换为数字图像的技术。这种技术对病理学领域具有革命性的意义&#xff0c;因为它允许病理…

Git分布式版本控制系统——在IDEA中使用Git(二)

四、IDEA中本地仓库的操作 1.将文件加入暂存区 2.将暂存区的文件提交到版本库(相当于git commit) 3.查看日志 五、IDEA中远程仓库的操作 1.查看远程仓库 2.添加远程仓库 3.推送至远程仓库 4.从远程仓库拉取

pyqt实现星三角减压启动

这个对于plc上实现是非常容易得。它本来就是逻辑控制器&#xff0c;如果用代码实现它&#xff0c;该怎么做呢&#xff1f;这个实现起来看似简单&#xff0c;实则是有不少坑的&#xff08;大神除外&#xff09;。我一直想用类来封装&#xff0c;让它继承QObject,为啥非要继承QOb…

为什么MySQL数据库超过2000万条数据,查询依然很快:B+树和数据页结构解析

MYSQL数据库单表建议最大2000万条数据&#xff0c;很多人都说如果超过了2000万条数据&#xff0c;性能就会下降的特别厉害。但是你实际上存储后&#xff0c;发现即使超过了2000万但是查询依旧很快&#xff0c;这是为什么&#xff1f; Mysql为了查询速度&#xff0c;内部使用了…

私域流量变现干货:轻松盘活,高效增长!

你知道如何增长私域流量并将这些流量转化为实际收益&#xff0c;让我们的品牌价值最大化吗&#xff1f; 今天&#xff0c;就分享几点干货&#xff0c;帮助大家盘活私域流量&#xff0c;实现高效增长&#xff01; 1、精准定位和用户画像 首先&#xff0c;了解您的私域流量源于…

JavaWeb开发03-Mybatis入门-基础操作-XML映射文件-动态SQL

一、Mybatis-入门 Java程序控制数据库 1.入门 定义实体类&#xff1a;一定要和表中的字段一一对应 配置连接数据库数据 建立Mapper层语句&#xff0c;来获取数据库数据以及将其封装到user的list中去。 2.配置SQL提示 为了进行查询数据库中有哪些表&#xff0c;所以得连接数据…

详解IP证书申请

申请IP证书&#xff0c;也被称为IP SSL证书&#xff0c;是一种特殊的SSL证书&#xff0c;它不同于传统的域名验证&#xff08;DV&#xff09;证书&#xff0c;是通过验证公网IP地址而不是域名来确保安全连接。这种证书用于保护IP地址&#xff0c;并在安装后起到加密作用。以下是…

VTK —— 一、Windows10下编译VTK源码,并用Vs2017代码测试(附编译流程、附编译好的库、vtk测试源码)

效果 编译 1、下载VTK8.2.0源码        2、解压源码后&#xff0c;进入目录创建build目录&#xff0c;同时在build内创建install目录 (下图install目录是在cmake第一次后才手动创建&#xff0c;建议在创建build时创建)        3、打开CMake&#xff0c;如下图填入…

CSS 这就是一个按照我看到的css ,边用边总结的笔记~

margin 和 paddingdisplay外部表现类(display-outside) : block , inline内部表现类(display-inside) : flex,gird,table,flow,flow-root,ruby margin 和 padding 可以设置1~4个属性 属性个数属性值1一起设置 上下左右2分别设置 上下 , 左右3分别设置 上 , 左右 , 下4分别设置…

戏作打油诗《无知》

笔者经营多年的《麻辣崇州论坛》&#xff0c;半月前突被攻击我在“霸屏”&#xff0c;没处讲理&#xff0c;特戏作打油诗《无知》一首&#xff0c;为那个无理取闹、砸我“麻辣崇州论坛”的无知小儿画像如下。 请点击链接&#xff0c;一目了然&#xff1a;崇州论坛-麻辣社区 没…

Gemini国内怎么使用

GPT、Claude、Gemini全系列模型国内使用方法来了&#xff01; 一直以来很多人问我能不能有个稳定&#xff0c;不折腾的全球AI大模型测试网站&#xff0c;既能够保证真实靠谱&#xff0c;又能够保证稳定、快速&#xff0c;不要老动不动就挂了、出错或者漫长的响应。 到目前为止…

Android T多屏多显——应用双屏间拖拽移动功能(更新中)

功能以及显示效果简介 需求&#xff1a;在双屏显示中&#xff0c;把启动的应用从其中一个屏幕中移动到另一个屏幕中。 操作&#xff1a;通过双指按压应用使其移动&#xff0c;如果移动的距离过小&#xff0c;我们就不移动到另一屏幕&#xff0c;否则移动到另一屏。 功能分析…

基于Python的微博舆论分析,微博评论情感分析可视化系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

ARM/X86+FPGA轨道交通/工程车辆行业的解决方案

深圳推出首条无人驾驶地铁—深圳地铁20号线&#xff0c;可以说是深圳地铁的一次开创性的突破。智能交通不断突破的背后&#xff0c;需要很严格的硬件软件等控制系 统&#xff1b;地铁无人驾驶意味着信号系统、通信系统、综合监控系统、站台屏蔽门工程等项目必须严格执行验收。…