【Linux 下】 信号量

news2024/11/17 13:35:12

文章目录

  • 【Linux 下】 信号量
      • 信号量概念
      • 信号量操作
        • 初始化和销毁
        • P()操作
        • V()操作
        • 理解PV操作
      • 基于信号量与环形队列实现的CS模型
        • 基于信号量和环形队列实现的生产者与消费者模型

【Linux 下】 信号量

信号量概念

信号量(Semaphore)是一种用于实现线程或进程之间同步和互斥的机制。它是由一个计数器和一组相关操作组成。

信号量中的计数器可以表示可用的资源数量或某种状态信息。线程或进程可以通过对信号量进行操作来进行等待或释放资源。

信号量的操作主要有两种:

  1. P(Wait)操作:如果计数器大于0,则将计数器减1;如果计数器为0,则等待,直到计数器大于0才能继续执行。
  2. V(Signal)操作:将计数器加1,并唤醒等待的线程或进程。

简单理解信号量,就可以将信号量理解为一个计数器,记录了当前临界资源的数量多少

简单理解

一个东西的出现,就必然有它出现的道理和用处,信号量可用于解决并发编程中的各种问题,例如资源的互斥访问、线程的同步等。通过合理地使用信号量,可以避免竞态条件死锁等并发编程中常见的问题。

补充:

信号量可以是计数信号量(Counting Semaphore)二进制信号量(Binary Semaphore)

  • 计数信号量:计数信号量可以取任意非负整数值,表示可用的资源数量。线程或进程可以通过 P 操作申请使用资源,通过 V 操作释放资源。当计数信号量的值为0时,表示资源已全部被占用,需要等待其他线程或进程释放资源。
  • 二进制信号量:二进制信号量只能取0或1两个值。常用于实现互斥锁的功能。当二进制信号量的值为1时,表示资源可用,线程或进程可以继续执行。当值为0时,表示资源已被占用,线程或进程需要等待。

下面介绍的是计数信号量


信号量操作

初始化和销毁

sem_init

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5GQmJgFH-1684512300347)(C:\Users\LANSHUHANG\AppData\Roaming\Typora\typora-user-images\image-20230315112821896.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WhSOCzL9-1684512300348)(C:\Users\LANSHUHANG\AppData\Roaming\Typora\typora-user-images\image-20230315113337002.png)]

作用:初始化信号量

参数:

  • sem: 指向我们所需要初始化信号量的指针,即指针里面存放的是我们所需要初始化的信号量的地址

  • pshared: 决定该信号量存放的位置,和被线程间分享还是进程间分享

  • **value 😗*信号量的初始值

返回值:

成功返回0,失败返回-1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-siYBSut0-1684512300348)(C:\Users\LANSHUHANG\AppData\Roaming\Typora\typora-user-images\image-20230315113651137.png)]

sem_destroy

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bCFw7XFv-1684512300348)(C:\Users\LANSHUHANG\AppData\Roaming\Typora\typora-user-images\image-20230315113751565.png)]

作用:销毁信号量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yf7zacnN-1684512300349)(C:\Users\LANSHUHANG\AppData\Roaming\Typora\typora-user-images\image-20230315114108917.png)]

参数:

  • sem: 所需要销毁的信号量的指针

返回值:

成功返回0,失败返回-1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WRE4Nr7K-1684512300349)(C:\Users\LANSHUHANG\AppData\Roaming\Typora\typora-user-images\image-20230315114133245.png)]

P()操作

sem_wait

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zUwWFTp4-1684512300349)(C:\Users\LANSHUHANG\AppData\Roaming\Typora\typora-user-images\image-20230315114246512.png)]

作用:等待信号量,对信号量进行P()操作(–sem),本质是申请使用临界资源[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PcAmmxan-1684512300349)(C:\Users\LANSHUHANG\AppData\Roaming\Typora\typora-user-images\image-20230315114703457.png)]

参数:

  • sem: 所要获取的信号量资源

返回值:

成功返回0,失败返回-1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-78ngFxTm-1684512300350)(C:\Users\LANSHUHANG\AppData\Roaming\Typora\typora-user-images\image-20230315114931665.png)]

V()操作

sem_post

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KWtQwoVu-1684512300350)(C:\Users\LANSHUHANG\AppData\Roaming\Typora\typora-user-images\image-20230315115042139.png)]

作用:对信号量进行V()操作(++sem)本质是释放临界资源[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Gw6AvFH-1684512300350)(C:\Users\LANSHUHANG\AppData\Roaming\Typora\typora-user-images\image-20230315115155306.png)]

参数:

  • sem: 所要获取的信号量资源

返回值

成功返回0,失败返回-1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VOC7HwJe-1684512300351)(C:\Users\LANSHUHANG\AppData\Roaming\Typora\typora-user-images\image-20230315115246400.png)]

理解PV操作

实际上p(),V()操作就是对信号量进行–,++操作,本质就是通过p()操作等待或者申请使用的临界资源,通过V()操作,将获得的临界资源归还或是释放;

从上面的理解我们不难得知,信号量也是临界资源,,而我们在前面的线程互斥曾说过,++,–并不是原子性操作,所以说sem的P(),V()操作实际上是极不安全的,这就说明对信号量的++,–操作是存在线程安全问题的;所以我们对信号量的操作是需要互斥锁


基于信号量与环形队列实现的CS模型

补充概念

**并行(Parallel)串行(Serial)**是描述多任务执行方式的概念。

串行指的是按顺序一个接一个地执行任务或指令。在串行执行中,每个任务或指令必须在前一个任务或指令完成之后才能开始执行。这意味着任务是按照线性顺序进行处理的,没有同时执行多个任务的能力。串行执行适用于那些互相依赖的任务,其中一个任务的结果需要作为输入传递给下一个任务。

并行是指同时执行多个任务或指令。在并行执行中,多个任务可以在同一时间段内同时进行,彼此之间相互独立。并行执行可以通过同时利用多个处理器核心、多线程或分布式计算等技术实现。并行执行可以大大提高任务的执行速度和系统的吞吐量,尤其适用于那些可以被分解成独立子任务的问题。

并行和串行之间存在一定的权衡和适用场景。串行执行简单直观,适用于顺序执行的任务,但可能导致执行时间较长。而并行执行可以提高任务的执行效率,但可能需要额外的并行处理能力和复杂的同步机制来确保正确性。

在现代计算机系统中,通常会同时应用并行和串行的概念。例如,一个程序可以使用串行方式执行某些任务,而使用并行方式执行某些可以并行处理的子任务。通过合理地组织和分配任务,可以充分利用系统资源,提高计算性能和效率。

并发(Concurrency)

**并发(Concurrency)**是指系统能够同时处理多个独立的任务、操作或事件的能力。并发的关键在于任务之间的重叠执行,不一定需要同时进行,但可以交替执行以提高效率和资源利用率。

并发可以在单个处理器上通过时间分片或任务切换实现,也可以利用多个处理器核心、多线程或分布式系统来实现。

并发编程涉及多个执行流(线程、进程或任务)同时执行,这些执行流可以独立运行并相互交互。并发编程的目标是保证多个执行流之间的正确同步和协调,避免竞态条件、死锁、饥饿等并发问题。

基于信号量和环形队列实现的生产者与消费者模型

合理的运用信号量可以实现线程并发操作

实现思路

  • 使用环形队列存储数据(逻辑上的数据:空位置和数据。配合指针来实现这个逻辑),充当“超市”,

    • 控制环形队列的精髓

      index %= _cap;
      
    • 指针:p_step– 代表生产者当前所处的坐标,通常指向的是空位置资源处;c_step–代表消费者当前所处的坐标,通常指向的是数据资源处

  • 通过互斥量和信号量维持生产者和消费者之间的关系

    • 消费者关注的是环形队列中的产品资源,只要有数据资源,就进行消费,没有数据资源,就停下里等待数据资源
    • 生产者关注的是环形对列中的空位置资源,只要有空位置,就进行“生产”,没有空位置,就停下等待空位置资源
    • 使用俩个信号量描述空位置资源和数据资源的状态(empty_sem,data_sem)
  • 规定环形队列大小为10,可以自行调节

  • 同步实现精髓:

    • p_step,c_step一开始都指向下标为0,代表生产者和消费同时出发,但得遵循以下规则
      • 消费者永远都是在追随生产者的步伐–
      • 生产者不能将消费者套圈–
    • 初始empty_sem为10,data_sem为0;

理解图

一些说明:

  • 因为生产者和消费者所关注的是不同的临界资源,所以使用俩把互斥锁,对不同的临界资源加锁,逻辑上更加合理,且效率更高
  • 再理解:多生产者和多消费者模型的优势实际上在于可以并行处理的和生产数据
    • 生产实际上分为俩步:1.生产数据 (通常是耗时的)2.分布数据(将数据添加到环形队列中)
    • 消费也是分为俩步:1.取出数据 2.处理数据(耗时)
    • 多个线程都能申请到临界资源(空位置资源或数据资源),而后就可以同时对数据处理;这是条件变量实现的cs模型所不能比拟的

主逻辑代码

#include <iostream>
#include "Ringqueue.hpp"
#include <unistd.h>
#include <time.h>
using namespace Lsh_ring_queue;


void* Produce(void* args)
{
    auto rq =(Ring_queue<int>* )args;
    while(1)
    {
        //生产:1. 生产数据 2.将数据添加到环形队列中
        int data=rand()%20+1;
        std::cout<<"生产者->"<<pthread_self()<<"生产了: "<<data<<std::endl;
        rq->Push(data);
        sleep(1);
    }
}
void* Comsumer(void * args)
{
    auto rq=(Ring_queue<int>*) args;
    while(1)
    {
        //消费: 1.从环形队列中取出数据 2.处理数据--此处只是进行了数据打印,实际中可以将该部分换成一些业务逻辑
        int data;
        rq->Pop(&data);
        std::cout<<"消费者->"<<pthread_self()<<"消费了: "<<data<<std::endl;
        sleep(1);
    }
}

int main()
{
    srand((unsigned int)time(nullptr));
    pthread_t p;
    pthread_t p1;
    pthread_t p2;
    pthread_t c;
    pthread_t c1;
    pthread_t c2;
    Ring_queue<int> * rq=new Ring_queue<int>();

    pthread_create(&p,nullptr,Produce,(void*)rq);
    pthread_create(&p1,nullptr,Produce,(void*)rq);
    pthread_create(&p2,nullptr,Produce,(void*)rq);
    pthread_create(&c,nullptr,Comsumer,(void*)rq);
    pthread_create(&c1,nullptr,Comsumer,(void*)rq);
    pthread_create(&c2,nullptr,Comsumer,(void*)rq);


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

    return 0;
}

环形队列实现代码:

#pragma once
#include <iostream>
#include <pthread.h>
#include <vector>
#include <semaphore.h>

namespace Lsh_ring_queue
{
    const int de_cap = 10;
    template <class T>
    class Ring_queue
    {
    private:
        int _cap;
        std::vector<T> _rq;

        sem_t _blank_sem; // 生产者所关心的信号量  初始值为10 代表10个空格 初始值为10
        sem_t _data_sem;  // 消费者所关系的信号量  初始值为0  代表0个数据  初始值为0

        int p_step; // 生产者和消费者所在的位置
        int c_step;

        pthread_mutex_t p_mtx;
        pthread_mutex_t c_mtx;
    public:
        Ring_queue()
        :_cap(de_cap) ,_rq(de_cap) ,p_step(0),c_step(0)
        {
            //第二个参数未0,代表在线程间共享该信号量
            sem_init(&_blank_sem,0,10);
            sem_init(&_data_sem,0,0);
            pthread_mutex_init(&p_mtx,nullptr);
            pthread_mutex_init(&c_mtx,nullptr);
        }
        void Push(T& in)
        {
            //对空位置进行减减操作
            sem_wait(&_blank_sem);
            pthread_mutex_lock(&p_mtx);

            _rq[p_step]=in;
            p_step++;
            p_step%=_cap; //更新生产者的位置

            pthread_mutex_unlock(&c_mtx);
            sem_post(&_data_sem);

        }
        void Pop(T* out)
        {
            sem_wait(&_data_sem);
            pthread_mutex_lock(&c_mtx);

            *out=_rq[c_step];
            c_step++;
            c_step%=_cap;
            
            pthread_mutex_unlock(&c_mtx);
            sem_post(&_blank_sem);
        }
        ~Ring_queue()
        {
            sem_destroy(&_blank_sem); 
            sem_destroy(&_data_sem); 
            pthread_mutex_destroy(&p_mtx); 
            pthread_mutex_destroy(&c_mtx); 
        }
    };
}

运行结果:

     pthread_mutex_unlock(&c_mtx);
        sem_post(&_blank_sem);
    }
    ~Ring_queue()
    {
        sem_destroy(&_blank_sem); 
        sem_destroy(&_data_sem); 
        pthread_mutex_destroy(&p_mtx); 
        pthread_mutex_destroy(&c_mtx); 
    }
};

}


**运行结果:**

![](https://img-blog.csdnimg.cn/img_convert/f99068201396b3ae7cb89b7577e41065.png)

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

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

相关文章

蓝莓投屏 - 超低延时投屏的投屏软件

蓝莓投屏是一个低延时投屏软件&#xff0c;支持安卓、iOS、Mac 设备与Windows系统的电脑之间互相投屏&#xff0c;包括手机/平板之间互投&#xff0c;手机投电脑&#xff0c;电脑投手机 等功能。 投屏画质达到4K高清&#xff0c;播放流畅无延迟。音视频同步&#xff0c;几乎没有…

无需OpenAI API Key,构建个人化知识库的终极指南

一、介绍 今天和大家一起学习下使用LangChain&#xff0b;LLM 来构建本地知识库。 我们先来了解几个名词。 二、什么是LLM&#xff1f; LLM指的是大语言模型&#xff08;Large Language Models&#xff09;&#xff0c;大语言模型&#xff08;LLM&#xff09;是指使用大量文…

Excel中正则表达式函数的使用

有这样一列 上海市闵行区七宝镇中春路7001号37栋 021-54881702 嘉定区黄渡镇金易路1号 021-69580001 如何将地址和电话分开 这两个分成2列&#xff08;地址和电话分开&#xff09; 第一列 第二列 上海市闵行区七宝镇中春路7001号37栋 021-54881702 嘉定区黄渡镇金易路1号 021-6…

【中阳期货】GPT-4正在改进自己,超强进化

GPT是一种预训练语言模型&#xff0c;由OpenAI研发。如果你希望快速了解GPT&#xff0c;可以按照以下步骤进行&#xff1a; 了解预训练语言模型&#xff1a;预训练语言模型是一种人工智能技术&#xff0c;可以通过大量语言数据的训练&#xff0c;自动学习语言的规律和语义。GPT…

web缓存Squid代理服务

缓存网页对象&#xff0c;减少重复请求 squid代理服务器&#xff0c;主要提供缓存加速&#xff0c;应用层过滤控制的功能 代理工作机制 1.代替客户机向网站请求数据&#xff0c;从而可以隐藏用户的真实ip地址 2.将获得的网页数据&#xff08;静态web元素&#xff09;保存到缓…

Rocky Linux 8.5 安装

Rocky Linux 是一个开源的企业级操作系统&#xff0c;旨在与 Red Hat Enterprise Linux 100% 1:1 兼容。 Rocky Linux 项目是什么? 下载地址 Rocky Linux 是一个社区化的企业级操作系统。其设计为的是与美国顶级企业 Linux 发行版实现 100&#xff05; Bug 级兼容&#xff…

【学习日记2023.5.12】之 自定义封装springboot-starter案例_SpringBoot监控_Web后端开发总结

文章目录 1. 自定义封装springboot-starter案例1.1 自定义starter分析1.2 自定义starter实现1.3 自定义starter测试 2. SpringBoot优势2.1 SpringBoot监控2.1.1 Actuator2.1.2 Springboot-Admin 2.2 小结 3. Web后端开发总结 1. 自定义封装springboot-starter案例 1.1 自定义s…

chatgpt赋能Python-python3怎么下载安装

Python 3的下载安装方法 Python 3是一种高级编程语言&#xff0c;具有简单易学、基本语法易于理解、大量的第三方库支持等特点&#xff0c;适用于各种软件开发项目。本文将教您如何下载和安装Python 3。 下载Python3 首先您需要在官网https://www.python.org/downloads/上下…

【新星计划·2023】单臂理由的原理讲解

单臂路由是指在路由器的一个接口上通过配置子接口的方式&#xff0c;实现原来互相隔离的VLAN之间可以互相通信。 一、单臂路由概述 网络中通过VLAN技术来实现隔离广播、方便管理及提高安全性等功能&#xff0c;一旦划分VLAN后&#xff0c;同—VLAN之间可以相互通信&#xff0…

一、通过命令行体验长安链

一、通过命令行体验长安链 1 、概述2、环境依赖2.1、硬件依赖2.2、软件依赖2.3、git安装2.4、golang安装2.5、gcc 3、环境搭建3.1、源码下载3.2、 源码编译3.3、编译及安装包制作3.4、启动节点集群3.5、查看节点启动使用正常 4、使用CMC命令行工具部署、调用合约4.1、编译&…

jenkins pipeline如何在一个任务中触发另外一个任务以及从下游任务获取文件

1、 前言 我们在创建jenkins任务的时候&#xff0c;有时候一个任务需要调用多个子任务来完成。比如&#xff0c;我们在编译某个镜像的时候&#xff0c;镜像由多个组件构成。那么我们就可以创建一个主任务以及多个子任务&#xff0c;主任务负责调用每个子任务&#xff0c;并将每…

PySide6/PyQT多线程之 线程池的基础概念和最佳实践

前言 在PySide6/PyQT 多线程编程中&#xff0c;线程池也是重要的一项知识点&#xff0c;线程池是一种高效管理和调度多个线程执行任务的方式。 通过结合线程池&#xff08;QThreadPool&#xff09;和任务类&#xff08;QRunnable&#xff09;&#xff0c;可以轻松地实现并发执行…

【分布族谱】正态分布和卡方分布的关系

文章目录 正态分布卡方分布卡方分布的极限 正态分布 正态分布&#xff0c;最早由棣莫弗在二项分布的渐近公式中得到&#xff0c;而真正奠定其地位的&#xff0c;应是高斯对测量误差的研究&#xff0c;故而又称Gauss分布。。测量是人类定量认识自然界的基础&#xff0c;测量误差…

FFmpeg命令实战(上)

标题 1.FFmpeg命令行环境搭建2.ffmpeg,ffplay和ffprobe的区别3.ffmpeg处理流程4.ffmpeg命令分类查询5.ffplay播放控制6.ffplay命令选项 1.FFmpeg命令行环境搭建 1.到达FFmpeg的github,选择下载需要的版本,这里以windows举例。 这里有两个压缩包&#xff0c;ffmpeg-master-lat…

Flutter 笔记 | Flutter Native 插件开发 (Android)

oh, 我亲爱的朋友&#xff0c;很高兴你来到了这里&#xff01;既然来了&#xff0c;那么就让我们在这篇糟糕的烂文章中&#xff0c;一起来学习一下&#xff0c;如何在一个糟糕的 Flutter 混合应用中开发一个糟糕的 Android Native 烂插件吧&#xff01;&#x1f611; 首先&…

研报精选230519

目录 【行业230519头豹研究院】2023年中国产后康复设备行业词条报告 【行业230519山西证券】有色金属行业周报&#xff1a;锂价快速回升&#xff0c;释放锂电行业复苏信号 【行业230519头豹研究院】2023年中国氢能重卡行业词条报告 【个股230519西南证券_森麒麟】腾飞的高端轮胎…

网页外包开发的测试方法及工具

网页开发的软件项目完成代码开发后需要进行全面的测试&#xff0c;这是正规的软件公司开发软件项目必须要做的工作&#xff0c;这方面有不少好用的工具供大家使用。今天和大家分享这方面的知识&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件…

Elasticsearch实战之处理邮件附件进行进行内容全文检索

目录 一、系统环境和软件要求 二、软件说明 三、定义文本抽取管道(pipeline) 四、建立索引设置文档结构映射 五、插入文档 六、查询文档 需求是将本地邮件内容以及PDF&#xff0c;EXCEL&#xff0c;WORD等附件内容进行处理&#xff0c;保存到ES数据库&#xff0c;实现邮件…

【I2C 通信的工作原理是什么?Arduino 和 I2C 教程】

【I2C 通信的工作原理是什么?Arduino 和 I2C 教程】 1. 概述2. I2C 的工作原理3. I2C 协议4. 例程5. Arduino I2C 代码在本教程中,我们将了解 I2C 通信协议的工作原理,我们还将使用 Arduino 板和使用该协议的传感器制作一个实际示例。您可以观看以下视频或阅读下面的书面教程…

chatgpt赋能Python-python3虚拟环境搭建

Python3虚拟环境搭建&#xff1a;介绍和步骤 Python是一门非常强大的编程语言&#xff0c;因此在许多不同类型的项目中都广泛使用。但是&#xff0c;不同项目可能需要使用不同版本的Python库和依赖项。这就是使用Python的虚拟环境的重要性&#xff0c;可以避免不同项目之间的冲…