Linux —— 信号量

news2024/11/17 11:41:06

Linux —— 信号量

  • 什么是信号量
      • P操作(Wait操作)
      • V操作(Signal操作)
      • 信号量的类型
    • 一些接口
      • POSIX 信号量接口:
      • 其他相关命令:
  • 基于循环队列的生产者和消费者模型
    • 同步关系
  • 多生产多消费

我们今天接着来学习信号量:

什么是信号量

信号量(Semaphore)是一种用于操作系统中管理共享资源访问和同步的机制。它是一种特殊的数据结构,用来控制多个进程或线程对公共资源的访问,以防止多个进程同时对同一资源进行访问而导致的冲突问题。信号量维护了一个计数器,该计数器可以增加(通常称为V操作或Signal操作)或减少(称为P操作或Wait操作),并且这些操作都是原子的,即不可中断。

P操作(Wait操作)

  • 当一个进程想要访问一个受保护的资源时,它会执行P操作。
  • P操作会检查信号量的值,如果信号量大于0,则减1,并允许进程继续执行。
  • 如果信号量等于0,表示资源已被占用,进程将被阻塞(等待)直到信号量的值变为非零。

V操作(Signal操作)

  • 当一个进程完成对资源的访问后,它会执行V操作。
  • V操作会将信号量的值加1,表示释放了一个资源。
  • 如果有其他进程因为之前P操作而等待,此时可能会唤醒其中一个等待的进程。

信号量的类型

  • 二值信号量:这种信号量只有0和1两种状态,相当于一个互斥锁,用于实现互斥访问
  • 计数信号量可以用于控制有限数量的相同资源的访问,计数器的初始值代表资源的数量。

简单来说,信号量的本质就是一个容量为N的锁,跟一般的锁不一样,它可以放个多个线程访问临界资源,但是达到上限就不会让线程进入了,而让他们阻塞等待。

一些接口

在Linux环境下,信号量作为进程间通信的一种手段,主要用于同步和互斥控制。以下是Linux下信号量的一些常用接口,主要涉及System V信号量和POSIX信号量两种类型:

POSIX 信号量接口:

  1. sem_init(sem_t *sem, int pshared, unsigned int value):
    在这里插入图片描述
  • 初始化一个POSIX信号量。sem 是信号量的地址,pshared 指定信号量是否可以在多进程中共享(如果为1则是共享的),value 是信号量的初始值。
  1. sem_wait(sem_t *sem):[
    在这里插入图片描述
  • 执行P操作,如果信号量的值大于0,则减1并继续执行;否则,进程将被阻塞直到信号量的值大于0
  1. sem_post(sem_t *sem):
    在这里插入图片描述
  • 执行V操作,增加信号量的值,如果因此唤醒了等待的进程,则会选择一个进行唤醒
  1. sem_destroy(sem_t *sem):
    在这里插入图片描述
  • 销毁一个POSIX信号量。
  1. sem_getvalue(sem_t *sem, int *sval):
    在这里插入图片描述
  • 获取POSIX信号量的当前值,sval 是存储信号量值的指针。

其他相关命令:

  • ipcs: 查看系统中所有的IPC设施状态,包括消息队列、共享内存段和信号量。
  • ipcrm: 删除指定的IPC设施,比如信号量集。

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

我们这里模拟实现一个基于循环队列的生产者和消费者模型,首先我们实现一个循环队列:

先把架子搭好:

#include<iostream>
#include<semaphore>
#include<pthread.h>
#include<cstring>
#include<vector>
#include<semaphore.h>
#include<unistd.h>


template<class T>
class CircleQueue
{
public:
    CircleQueue()
        :_size(10)
        ,_product_start(0)
        ,_consum_start(0)
    {

    }

    ~CircleQueue()
    {

    }



private:
};

现在,我们要分析一下,这里面的同步关系:

同步关系

我们这里清楚,生产者会消费一个空间生产一个产品,并往前走一步,并且生产者和消费者是指向一个空间:
在这里插入图片描述在这里插入图片描述消费者会消费一个产品,腾出一个空间资源,然后往前走一步
在这里插入图片描述这个时候,我们可以分析出以下几条信息:

  1. 生产者一定先跑,因为一开始有空间资源,没有产品资源。
  2. 消费者一定比生产者跑的慢,因为消费者的速度是受生产者产出产品的速度决定的。
  3. 当空间资源被用完时,生产者停止生产,让消费者消费之后,腾出空间资源之后再继续生产。相反,如果没有产品资源,消费者阻塞,让生产者产出产品之后,再消费产品。
#include <iostream>
#include <semaphore>
#include <pthread.h>
#include <cstring>
#include <vector>
#include <semaphore.h>
#include <unistd.h>

// 定义一个泛型循环队列类,利用信号量实现线程安全的生产者消费者模型
template <class T>
class CircleQueue {
public:
    // 默认构造函数,初始化一个大小为10的循环队列
    CircleQueue()
        : _size(10), // 队列默认容量
          _product_start(0), // 生产者开始位置
          _consum_start(0) // 消费者开始位置
    {
        // 初始化空间信号量,初始值为队列大小,表示可用空间数量
        sem_init(&_sem_space, 0, _size);
        // 初始化数据信号量,初始值为0,表示当前没有可消费的数据
        sem_init(&_sem_data, 0, 0);
        // 初始化队列容器
        _queue.resize(_size);
    }

    // 带参数构造函数,允许用户自定义队列大小
    CircleQueue(int size)
        : _size(size), // 用户指定的队列容量
          _product_start(0),
          _consum_start(0)
    {
        sem_init(&_sem_space, 0, size); // 根据用户指定的大小初始化空间信号量
        sem_init(&_sem_data, 0, 0);
        _queue.resize(size);
    }

    // 析构函数,释放信号量资源
    ~CircleQueue() {
        sem_destroy(&_sem_space);
        sem_destroy(&_sem_data);
    }

    // 生产者方法,向队列中添加数据
    void Push(const T& data) {
        // 在尝试放入数据前,先等待确保有空闲空间
        sem_wait(&_sem_space);
        // 将数据放入队列的下一个生产位置
        _queue[_product_start] = data;
        // 更新生产者位置,并对索引取模以实现循环
        _product_start = (_product_start + 1) % _size;
        // 数据放入后,释放数据信号量,通知消费者有新数据可取
        sem_post(&_sem_data);
    }

    // 消费者方法,从队列中取出数据
    void Pop(T* out) {
        // 等待直到有数据可消费
        sem_wait(&_sem_data);
        // 从队列的下一个消费位置取出数据
        *out = _queue[_consum_start];
        // 更新消费者位置,并对索引取模实现循环
        _consum_start = (_consum_start + 1) % _size;
        // 数据取出后,释放空间信号量,表明队列中有更多空间可填充
        sem_post(&_sem_space);
    }

private:
    // 循环队列的底层数据结构
    std::vector<T> _queue;
    int _size; // 队列的最大容量

    // 生产者和消费者的当前位置索引
    int _product_start;
    int _consum_start;

    // 信号量用于同步控制
    sem_t _sem_space; // 控制队列中的空闲空间
    sem_t _sem_data; // 控制队列中的有效数据量
};

这段代码实现了一个基于信号量的线程安全循环队列模板类。它支持生产者线程向队列中添加元素(通过Push方法),同时允许消费者线程从队列中取出元素(通过Pop方法)。通过使用两个信号量——_sem_space_sem_data——分别管理队列的可用空间和有效数据量,确保了多线程环境下的正确同步与互斥。

基于这个,我们实现一下整体的代码:

#include"CircleQueue.hpp"
#include<time.h>

void* product(void* args)
{
    CircleQueue<int>* cq = static_cast<CircleQueue<int>*>(args);

    while(true)
    {
        //生产数据
        int randomdata = rand() % 10 + 1;
        cq->Push(randomdata);
        std::cout << "Producter has product a number: " << randomdata <<
        std::endl; 
        
    }

    return nullptr;
}

void* consum(void* args)
{
    CircleQueue<int>* cq = static_cast<CircleQueue<int>*>(args);
    while(true)
    {
        //拿出数据
        int outnumber = 0;
        cq->Pop(&outnumber);
        std::cout << "Consum gets a number: " << outnumber <<
        std::endl; 
        sleep(1);
    }

    return nullptr;
}

int main()
{
    srand(time(0));
    //创建线程
    pthread_t tid_product,tid_consum;
    
    CircleQueue<int>*cq = new CircleQueue<int>();
    //生产者
    pthread_create(&tid_product,nullptr,product,cq);

    //消费者
    pthread_create(&tid_consum,nullptr,consum,cq);

    pthread_join(tid_product,nullptr);
    pthread_join(tid_consum,nullptr);
}

在这里插入图片描述
大家可以调整一下生产者或者消费者的速度,看看情况怎么样。

多生产多消费

这里注意一下,这里和互斥锁的情况有点不一样:

我们用一个通俗的例子来解释:

假设有一天,你和你的好朋友(一共8个人),想去电影院看电影:
在这里插入图片描述
你们到售票机哪里去买票,此时电影院的座位很充足,所以你们都买到票了。
在这里插入图片描述但是一看座位号,发现大家全都是1号座位

在这里插入图片描述
这就很尴尬了,这个场景可以类比到我们上面的代码中,8个线程通过了信号量,但是都在往一个位置位置放东西,这样不行,所以我们得出位置是每个人独有的,一人一份,如果自己拥有,别人就不能拥有,所以为了保证每一个位置为一人独有所以我们要给每个位置上锁(电影院的座位)

如果有点绕,咋们来复盘一下:

  1. 电影院有很多位置,所以,我们多人可以都得到属于自己的位置(类比我们的循环队列)
  2. 为了保证我们的位置是独一无二属于自己,我们要给自己的位置上锁,保证只有自己可以坐这个座位。(类比循环队列中的下标)

解决完上面的问题,我们现在要做的,就是还要两把锁,一个保证生产的时候,放入时候的位子只属于一个生产者进程,另一把锁保证从一个位置里面拿产品的时候只属于一个消费者进程

#include <iostream>
#include <semaphore>
#include <pthread.h>
#include <cstring>
#include <vector>
#include <semaphore.h>
#include <unistd.h>

// 定义一个泛型循环队列类,结合信号量与互斥锁实现线程安全的生产者消费者模型
template <class T>
class CircleQueue {
public:
    // 默认构造函数,初始化一个大小为10的循环队列,并初始化信号量与互斥锁
    CircleQueue()
        : _size(10), // 队列默认容量
          _product_start(0), // 生产者开始位置
          _consum_start(0) // 消费者开始位置
    {
        sem_init(&_sem_space, 0, _size); // 初始化空间信号量,初始值为队列大小
        sem_init(&_sem_data, 0, 0); // 初始化数据信号量,初始值为0
        pthread_mutex_init(&_p_mutex, nullptr); // 初始化生产者互斥锁
        pthread_mutex_init(&_c_mutex, nullptr); // 初始化消费者互斥锁
        _queue.resize(_size); // 初始化队列向量
    }

    // 带参数构造函数,允许自定义队列大小
    CircleQueue(int size)
        : _size(size),
          _product_start(0),
          _consum_start(0)
    {
        sem_init(&_sem_space, 0, size);
        sem_init(&_sem_data, 0, 0);
        pthread_mutex_init(&_p_mutex, nullptr);
        pthread_mutex_init(&_c_mutex, nullptr);
        _queue.resize(size);
    }

    // 析构函数,释放信号量与互斥锁资源
    ~CircleQueue() {
        sem_destroy(&_sem_space);
        sem_destroy(&_sem_data);
        pthread_mutex_destroy(&_p_mutex);
        pthread_mutex_destroy(&_c_mutex);
    }

    // 生产者方法,向队列中添加数据
    void Push(const T& data) {
        // 确保有足够的空间再进行生产
        sem_wait(&_sem_space);
        // 使用互斥锁保护生产过程,防止与其它生产者并发冲突
        pthread_mutex_lock(&_p_mutex);
        _queue[_product_start] = data; // 添加数据
        _product_start = (_product_start + 1) % _size; // 更新生产者位置
        pthread_mutex_unlock(&_p_mutex); // 释放锁
        sem_post(&_sem_data); // 数据添加完毕,释放数据信号量
    }

    // 消费者方法,从队列中取出数据
    void Pop(T* out) {
        // 确保有数据可消费
        sem_wait(&_sem_data);
        // 使用互斥锁保护消费过程
        pthread_mutex_lock(&_c_mutex);
        *out = _queue[_consum_start]; // 取出数据
        _consum_start = (_consum_start + 1) % _size; // 更新消费者位置
        pthread_mutex_unlock(&_c_mutex); // 释放锁
        sem_post(&_sem_space); // 释放空间信号量
    }

private:
    // 循环队列的底层数据结构
    std::vector<T> _queue;
    int _size; // 队列的最大容量

    // 生产者和消费者的当前位置索引
    int _product_start;
    int _consum_start;

    // 同步控制工具
    sem_t _sem_space; // 控制队列中的空闲空间
    sem_t _sem_data; // 控制队列中的有效数据量

    // 互斥锁用于保护队列访问的原子性
    pthread_mutex_t _p_mutex; // 生产者使用的互斥锁
    pthread_mutex_t _c_mutex; // 消费者使用的互斥锁
};

这里打印的时候,由于屏幕也是公共资源,我这里加锁,是保证打印的时候,只有生产者或者消费者打印信息:

#include"CircleQueue.hpp"
#include<time.h>

pthread_mutex_t global_mutex = PTHREAD_MUTEX_INITIALIZER;

void* product(void* args)
{
    CircleQueue<int>* cq = static_cast<CircleQueue<int>*>(args);

    while(true)
    {
        //生产数据
        int randomdata = rand() % 10 + 1;
        cq->Push(randomdata);


        pthread_mutex_lock(&global_mutex);
        std::cout << "Producter has product a number: " << randomdata <<
        std::endl; 
        pthread_mutex_unlock(&global_mutex);
    }

    return nullptr;
}

void* consum(void* args)
{
    CircleQueue<int>* cq = static_cast<CircleQueue<int>*>(args);
    while(true)
    {
        //拿出数据
        int outnumber = 0;
        cq->Pop(&outnumber);

        pthread_mutex_lock(&global_mutex);
        std::cout << "Consum gets a number: " << outnumber <<
        std::endl; 
        pthread_mutex_unlock(&global_mutex);
        sleep(1);
    }

    return nullptr;
}

int main()
{
    srand(time(0));
    //创建线程
    pthread_t tid_product[8],tid_consum[8];
    
    CircleQueue<int>*cq = new CircleQueue<int>();

    //生产者
    for(int i = 0; i < 8; i++)
    {
        pthread_create(&tid_product[i],nullptr,product,cq);
    }
    

    //消费者
    for(int i = 0; i < 8; i++)
    {
        pthread_create(&tid_consum[i],nullptr,consum,cq);
    }
 
    for(int i = 0; i < 8; i++)
    {
        pthread_join(tid_product[i],nullptr);
    }
    
    for(int i = 0; i < 8; i++)
    {
        pthread_join(tid_consum[i],nullptr);
    }
    
}

在这里插入图片描述

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

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

相关文章

5.22R语言初步学习-1

今天上课讲R语言&#xff0c;要干什么没讲&#xff0c;分析什么&#xff0c;目的是什么没讲。助教基本上就是让我们打开窗口&#xff0c;按要求抄代码指令&#xff0c;代码原理也没讲......再加上最近正好在学概率论与数理统计&#xff0c;肯定是有用的&#xff0c;所以还是学习…

(全面)Nginx格式化插件,Nginx生产工具,Nginx常用命令

目录 &#x1f3ab; 前言 &#x1f389; 开篇福利 &#x1f381; 开篇福利 x2 Double happiness # 介绍 # 地址 # 下载 &#x1f4bb; 命令及解析 # 整个文件系统中搜索名为nginx.conf的文件 # 编辑nginx.conf文件 # 重新加载配置文件 # 快速查找nginx.conf文件并使…

HTTP协议请求报头(header)

目录 目录 User-Agent与Referer User-Agent Referer Cookie HTTP报头&#xff08;header&#xff09;的结构是“键值对”结构&#xff0c;每个键值对占一行&#xff0c;键和值之间使用分号分隔。 报头的种类有很多&#xff0c;本次介绍几个重要的。 Host&#xff1a;表示请…

中电金信:从系统升级入手 看这些银行如何激活信贷业务

近期&#xff0c;我国金融机构围绕科技金融、绿色金融、普惠金融、养老金融、数字金融五篇大文章&#xff0c;持续优化信贷结构&#xff0c;不断增强金融服务实体经济作用。金融业认真落实国家经济发展目标&#xff0c;不断优化资金投向结构&#xff0c;持续加大重大战略、重点…

robosuite导入自定义机器人

目录 目的&#xff1a;案例一&#xff1a;成果展示具体步骤&#xff1a;URDF文件准备xml文件生成xml修改机器人构建 目的&#xff1a; 实现其他标准/非标准机器人的构建 案例一&#xff1a; 成果展示 添加机器人JAKA ZU 7 这个模型 具体步骤&#xff1a; URDF文件准备 从…

解决小皮面版搭建php网站数据库连接不了

首先进入mysql bin目录下 并执行cmd mysql -u root -pCREATE USER userlocalhost IDENTIFIED BY pass;GRANT ALL PRIVILEGES ON *.* TO userlocalhost;GRANT SELECT, INSERT, UPDATE ON database_name.* TO xxwlocalhost;FLUSH PRIVILEGES;select host ,user from mysql.user…

数据防泄漏系统哪个好用,给文件加密的软件

数据防泄露&#xff08;Data Leakage Prevention&#xff0c;DLP&#xff09;是指通过一定的技术手段&#xff0c;防止组织指定&#xff08;重要或敏感的&#xff09;数据或信息资产以违反安全策略规定的形式流出组织的一种策略。 信息防泄露以文档加密技术为核心&#xff0c;…

urllib_post请求_百度翻译

打开百度翻译&#xff0c;并打开控制台&#xff0c;输入spider&#xff0c;然后在网络中找到对应的接口&#xff0c;可以看出&#xff0c;该url是post请求 在此案例中找到的接口为sug&#xff0c;依据为&#xff1a; 可以看到&#xff0c;传递的数据为kw : XXX&#xff0c; 所…

Excel 下划线转驼峰

Excel 下划线转驼峰 LOWER(LEFT(SUBSTITUTE(PROER(A1),"_",""),1))&RIGHT(SUBSTITUTE(PROPER(A1),"_",""),LEN(SUBSTITUTE(PROPER(A1),"_",""))-1)

内脏油脂是什么?如何减掉?

真想减的人&#xff0c;减胖是很容易的&#xff0c;但想要形体美又健康&#xff0c;还是得从减内脏油脂开始&#xff0c;那么&#xff0c;问题来了&#xff0c;什么是内脏油脂&#xff1f; 油脂它分部于身体的各个角落&#xff0c;四肢、腹部、腰、臀部、脸、脖子...等&#xf…

Nginx实现负载均衡与故障检查自动切换

创作灵感来源于个人项目的一个稳定性规划&#xff0c;单节点的项目稳定性方面可能有很大的缺漏&#xff0c;因此需要升级为多节点&#xff0c;保证服务故障后&#xff0c;依然有其他服务可用&#xff0c;不会给前端用户造成影响。 &#xff08;前面讲选型&#xff0c;想直接看…

传统蓝牙模块BR/EDR与低功耗蓝牙模块有什么区别?

传统蓝牙模块BR/EDR与低功耗蓝牙模块有什么区别&#xff1f;下面跟随美迅物联网MesoonRF从多个维度来了解。   概述&#xff1a;低功耗蓝牙采用了高斯频移键控&#xff08;GFSK&#xff09;。这里我们先抛开蓝牙的协议&#xff0c;单纯从Radio的角度看收发通信&#xff0c;Ra…

新品 | Forge® 1GigE IP67工业相机助力智能农业、食品和饮料行业

近日&#xff0c;51camera的合作伙伴Teledyne FLIR IIS推出Forge 1GigE IP67,它是Forge系列的最新工业相机&#xff0c;旨在在恶劣的工业环境中运行&#xff0c;同时确保高效的生产能力。Forge 1GigE IP67致力于为工厂自动化提供先进成像系统的最新产品。 Forge 1GigE IP67相机…

【设计模式深度剖析】【3】【创建型】【抽象工厂模式】| 要和【工厂方法模式】对比加深理解

&#x1f448;️上一篇:工厂方法模式 | 下一篇:建造者模式&#x1f449;️ 目录 抽象工厂模式前言概览定义英文原话直译什么意思呢&#xff1f;&#xff08;以运动型车族工厂&#xff0c;生产汽车、摩托产品为例&#xff09; 类图4个角色抽象工厂&#xff08;Abstract Fac…

美业系统源码美业SaaS系统-门店卡项已线下退款,需要作废怎么处理?

美业SaaS系统源码 连锁门店美业收银系统源码 收银管理 / 会员管理 / 预约管理 / 排班管理 / 商品管理 / 活动促销 PC管理后台、手机APP、iPad APP、微信小程序 1、加盟店卡项线下退款处理方法&#xff1a; 询问具体退款会员手机号和卡项&#xff0c;找到需要退款的订单号。…

jenkins自动化部署详解

一、准备相关软件 整个自动化部署的过程就是从git仓库拉取最新代码&#xff0c;然后使用maven进行构建代码&#xff0c;构建包构建好了之后&#xff0c;通过ssh发送到发布服务的linux服务器的目录&#xff0c;最后在此服务器上执行相关的linux命令进行发布。 此篇文章jenkins…

优思学院|六西格玛在人力资源管理(HR)的应用指南

有效的HR流程管理对于组织的成功至关重要。然而&#xff0c;许多组织在HR效率方面存在困难&#xff0c;导致员工流动率高、工作放弃率高、生产力低下、缺勤率高以及盈利能力下降。 六西格玛方法论可以用来识别改进领域并实施变革&#xff0c;从而使HR功能更加高效和有效。 这…

监控上网的软件有哪些?含泪推荐的电脑监控软件

监控上网的软件有很多&#xff0c;企业选择的时候应该遵循什么样的原则呢&#xff1f;鄙人愚见&#xff0c;认为以下四项原则是选择监控软件时首要考虑的。 1、功能需求&#xff1a; 监控软件不应该只是起到控制上网的作用&#xff0c;因为一些泄密行为可能是通过USB接口、打印…

TI C2000 FLASH 模拟 EEPROM

简述 FLASH和EEPROM同为非易失存储器,互有优势。 FLASH Flash是非易失性存储器(NVM)的一种形式。相对于EEPROM,Flash具有更高的存储密度和更快的写入速度。Flash内部被分为多个扇区,每个扇区都可以单独擦除和写入。但是寿命相比EEPROM较短,以TI芯片为例,flash擦写次数在…

Linux下的权限

目录 1.shell命令以及运行原理 1.1原理上初步理解shell外壳 1.1.1为什么要有shell外壳 1.1.2shell外壳是什么 1.1.3怎么办&#xff08;shell外壳的基本运行原理&#xff09; 2.Linux下的用户 3.Linux权限管理 3.1.文件访问者的分类&#xff08;人&#xff09; 3.2…