【Linux —— POSIX信号量 - 基于环形队列的生产消费模型】

news2024/11/13 8:03:20

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

  • POSIX信号量
    • 信号量的概念
    • POSIX信号量的类型
    • 信号量的操作
  • POSIX信号量函数
  • 基于环形队列的生产消费模型
    • 设计思路
    • 同步和安全性
    • 代码

POSIX信号量

信号量的概念

 POSIX信号量是一种用于进程和线程之间同步的机制,主要用于控制对共享资源的访问。信号量是一种同步原语,通常表现为一个整数值,表示可用资源的数量。信号量的值不小于0。如果一个进程尝试将信号量的值减少到小于0,操作将被阻塞,知道信号量的值增加到允许的范围。

POSIX信号量的类型

POSIX信号量主要分为两种类型:

  1. 命名信号量:
    • 具有一个名称,可以通过该名称在不同的进程间访问。命名信号量通常用于进程间的同步。
    • 使用sem_open()函数创建或打开命名信号量
      2.未命名信号量:
    • 不具有名称,通常存在于内存中,适用于同一进程内的多个进程之间的同步。
    • 未命名信号量可以通过sem_init()函数进行初始化。

信号量的操作

信号量的基本操作包括:

  • P操作(等待操作):使用sem_wait()函数实现,尝试减少信号量的值。如果信号量的值为0,则调用线程将被阻塞,直到信号量的值大于0。
  • V操作(释放操作):使用sem_post()函数实现,增加信号量的值,通知其他等待的线程或进程信号量的可用性。

POSIX信号量函数

POSIX信号量提供了一组函数来创建、操作和销毁信号量。以下是一些常用的POSIX信号量函数及其参数和返回值:

  1. sem_init

功能: 初始化未命名信号量。
原型:

int sem_init(sem_t *sem, int pshared, unsigned int value);

参数:

  • sem: 指向信号量对象的指针。
  • pshared: 如果为0,则信号量仅用于线程间同步;如果非0,则信号量可用于进程间同步。
    value: 信号量的初始值。

返回值:

  • 成功时返回0;失败时返回-1,并设置errno以指示错误类型。
  1. sem_destroy

功能: 销毁信号量。
原型:

int sem_destroy(sem_t *sem);

参数:

  • ·sem·: 指向要销毁的信号量对象的指针。
    返回值:
  • 成功时返回0;失败时返回-1,并设置errno。
  1. sem_wait

功能: 进行 P 操作(等待操作),尝试获取信号量。
原型:

int sem_wait(sem_t *sem);

参数:

  • sem: 指向信号量对象的指针。

返回值:

  • 成功时返回0;失败时返回-1,并设置errno。如果信号量的值为0,调用线程将被阻塞。
  1. sem_post
  • 功能: 进行V操作(释放操作),增加信号量的值。
  • 原型:
int sem_post(sem_t *sem);

参数:

  • sem: 指向信号量对象的指针。

返回值:

  • 成功时返回0;失败时返回-1,并设置errno。

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

下面通过STL的vector来设计一个环形队列,环形队列采用数组模拟,用模运算来模拟环状特性:
在这里插入图片描述
环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或者标记位来
判断满或者空。另外也可以预留一个空的位置,作为满的状态
asd

但是我们现在有信号量这个计数器,就很简单的进行多线程间的同步过程

环形队列实际上就是一个链表,只是在理解上理解为一个环形的:
在这里插入图片描述

设计思路

  1. 使用环形队列:
  • 环形队列是一种循环使用固定大小内存的数据结构,非常适合生产者-消费者模型。
  • 通过维护生产者指针(_p_step)和消费者指针(_c_step),可以实现对环形队列的环形访问
  1. 使用信号量:
  • 使用两个信号量:_data_sem(数据信号量)和_space_sem(空间信号量)。
  • _data_sem表示队列中可用数据的数量,初始值为0。
  • _space_sem表示队列中可用空间的数量,初始值为队列的最大容量(_max_cap)
  1. 使用互斥量:
  • 使用两个互斥量:_p_mutex(生产者锁)和_c_mutex(消费者锁)。
  • 互斥量用于保护生产者和消费者对队列的并发访问

同步和安全性

  1. 生产者:
  • 生产者首先使用 P (_space_sem) 申请可用空间,如果没有可用空间,会被阻塞
  • 获取_p_mutex锁,保护对队列的写入操作。
  • 将数据写入队列,并更新生产者指针_p_step
  • 释放_p_mutex锁。
  • 使用 V (_data_sem) 发布一个数据可用信号。
  1. 消费者:
  • 消费者首先使用 P (_data_sem) 申请可用数据,如果没有可用数据,会被阻塞。
  • 获取_c_mutex锁,保护对队列的读取操作。
  • 从队列中读取数据,并更新消费者指针_c_step
  • 释放_c_mutex锁。
  • 使用 V (_space_sem)发布一个空间可用信号。
  1. 同步和安全性:
  • 信号量确保了生产者和消费者对队列的访问是同步的。
  • 生产者在有可用空间时才能写入,消费者在有可用数据时才能读取。
  • 互斥量确保了生产者和消费者对队列的并发访问是安全的。
  • 每个线程在访问队列时都会获取相应的互斥量,保证了临界区的互斥访问。
  1. 其他考虑:
  • 在构造函数中初始化信号量和互斥量。
  • 在析构函数中销毁信号量和互斥量。
  • 确保在异常情况下,信号量和互斥量也能被正确销毁。

代码

  • RingQueue.hpp 环形队列的实现
#pragma once

#include <iostream>
#include <vector>
#include <string>
#include <pthread.h>
#include <semaphore.h>

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

public:
    RingQueue(int max_cap) : _ringqueue(max_cap), _max_cap(max_cap), _c_step(0), _p_step(0)
    {
        sem_init(&_data_sem, 0, 0);
        sem_init(&_space_sem, 0, max_cap);

        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);
        _ringqueue[_p_step] = in;
        _p_step++;
        _p_step %= _max_cap;
        pthread_mutex_unlock(&_p_mutex);
        V(_data_sem);
    }

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

    ~RingQueue()
    {
        sem_destroy(&_data_sem);
        sem_destroy(&_space_sem);

        pthread_mutex_destroy(&_c_mutex);
        pthread_mutex_destroy(&_p_mutex);
    }

private:
    std::vector<T> _ringqueue;
    int _max_cap;

    int _c_step;
    int _p_step;

    sem_t _data_sem;  // 消费者   数据信号量
    sem_t _space_sem; // 生产者   空间信号量

    pthread_mutex_t _c_mutex;
    pthread_mutex_t _p_mutex;
};

  • Task.hpp Task类的实现
#pragma once
#include <iostream>

class Task
{
public:
    Task()
    {}
    Task(int x , int y):_x(x),_y(y)
    {
    }
    ~Task()
    {}
    void Excute()
    {
        _result = _x + _y;
    }
    std::string Debug()
    {
        std::string msg = std::to_string(_x) + " + " + std::to_string(_y) + " = " + " ? ";
        return msg;
    }
    std::string Result()
    {
        std::string msg = std::to_string(_x) + " + " + std::to_string(_y) + " = " + std::to_string(_result);
        return msg;
    }
    void operator()()
    {
        Excute();
    }
private:
    int _x;
    int _y;
    int _result;
};
  • main.cc 代码上层的调用逻辑
#include "RingQueue.hpp"
#include "Task.hpp"
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <ctime>

void *Consumer(void*args)
{
    RingQueue<Task> *rq = static_cast<RingQueue<Task> *>(args);
    while(true)
    {
        Task t;
        // 1. 消费
        rq->Pop(&t);

        // 2. 处理数据
        t();
        std::cout << "Consumer-> " << t.Result() << std::endl;
    }
}
void *Productor(void*args)
{
    RingQueue<Task> *rq = static_cast<RingQueue<Task> *>(args);

    while(true)
    {
        sleep(1);

        // 1. 构造数据
        int x = rand() % 10 + 1; //[1, 10]
        usleep(x*1000);
        int y = rand() % 10 + 1;
        Task t(x, y);

        // 2. 生产
        rq->Push(t);

        std::cout << "Productor -> " << t.Debug() << std::endl;
    }
}

int main()
{
    srand(time(nullptr) ^ getpid());
    RingQueue<Task> *rq = new RingQueue<Task>(5);
    // 单单
    pthread_t c1, c2, p1, p2, p3;
    pthread_create(&c1, nullptr, Consumer, rq);
    pthread_create(&c2, nullptr, Consumer, rq);
    pthread_create(&p1, nullptr, Productor, rq);
    pthread_create(&p2, nullptr, Productor, rq);
    pthread_create(&p3, nullptr, Productor, rq);


    pthread_join(c1, nullptr);
    pthread_join(c2, nullptr);
    pthread_join(p1, nullptr);
    pthread_join(p2, nullptr);
    pthread_join(p3, nullptr);
    return 0;
}

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

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

相关文章

【网络】网络层协议——IP协议

目录 1.TCP和IP的关系 2.IP协议报文 2.1. 4位首部长度&#xff0c;16位总长度&#xff0c;8位协议 2.2. 8位生存时间 &#xff0c;32位源IP地址和32位目的IP地址 3.IP地址的划分 3.1.IP地址的表现形式 3.2.旧版IP地址的划分 3.2.1.旧版IP地址的划分思路 3.2.2.分类划…

各种注意力评分函数的实现

预备知识 本文基于MXNet进行实现&#xff0c;需要对于注意力机制有一定初步了解。也需要对Python有足够了解。 另外这里稍加说明&#xff0c;在注意力机制中&#xff0c;本质上是“注意”的位置&#xff0c;即加权计算后进行Softmax回归的结果。在Nadaraya-Watson核回归中&am…

问界M7 Pro发布,又做回纯视觉?华为智驾系统这3年到底功夫下在哪?

北京时间8月26日下午14:00&#xff0c;华为又公布了一款新问界车型&#xff0c;时至今日&#xff0c;华为问界家族已有三大款&#xff0c;细分9个系列车型&#xff08;从动力方面看&#xff0c;各自都分为增程和纯电两种版本&#xff09;。 1个多小时的发布会上&#xff0c;除…

Zabbix和Prometheus

1.Zabbix 1.1 Zabbix监控获取数据的方式 zabbix-agent 适用于服务器&#xff0c;主机监控 SNMP协议 适用于网络设备&#xff08;交换机、路由器、防火墙&#xff09; IPMI协议 适用于监控硬件设备信息&#xff08;温度、序列号&#xff09; JMX协议 适用于Java应用监控 1.2 …

基于SSM+微信小程序的跑腿平台管理系统(跑腿3)(源码+sql脚本+视频导入教程+文档)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于SSM微信小程序的跑腿平台管理系统实现了管理员、接单员及用户三个角色。 1、管理员实现了首页、个人中心、管理员管理、基础数据管理、接单详情、跑腿任务管理等。 2、接单员实现了…

C++ TinyWebServer项目总结(14. 多线程编程)

早期Linux不支持线程&#xff0c;直到1996年&#xff0c;Xavier Leroy等人开发出第一个基本符合POSIX标准的线程库LinuxThreads&#xff0c;但LinuxThreads效率低且问题多&#xff0c;自内核2.6开始&#xff0c;Linux才开始提供内核级的线程支持&#xff0c;并有两个组织致力于…

离线环境下的 Prometheus 生态部署攻略

一、前言 在当今高度数字化的世界中&#xff0c;监控系统的稳定性和可靠性对于确保业务连续性和性能优化至关重要。特别是在网络隔离或无互联网接入的局域网环境下&#xff0c;离线部署监控解决方案成为了一种必要且挑战性的任务。本文将深入探讨如何在离线环境中成功部署 Pro…

深圳保障房、商品房、小产权房子类型对比

摘要&#xff1a; 整理了我认知以内的深圳房子类型&#xff0c;有安居房&#xff0c;可售人才房&#xff0c;共有产权房、配售型保障房、商品房、统建楼、农民房的区别。如果数据存疑&#xff0c;可以多方对比论证&#xff0c;我也主要靠百度。 我发现我很多同事是非深户&#…

秋招突击——算法练习——8/26——图论——200-岛屿数量、994-腐烂的橘子、207-课程表、208-实现Trie

文章目录 引言正文200-岛屿数量个人实现 994、腐烂的橘子个人实现参考实现 207、课程表个人实现参考实现 208、实现Trie前缀树个人实现参考实现 总结 引言 正文 200-岛屿数量 题目链接 个人实现 我靠&#xff0c;这道题居然是腾讯一面的类似题&#xff0c;那道题是计算最…

《分析模式》2024中译本-前言-01(加红色标注)

写在前面 今天开始&#xff0c;我们逐渐发布一些《分析模式》2024中译本的译文。 红色字体标出的文字&#xff0c;表示我认为之前的译本可能会让读者产生误解的地方。 感兴趣的读者&#xff0c;可以对照之前译本以及原文&#xff0c;捉摸一下为什么要标红。 主要原因当然是…

基于SpringBoot+Vue+MySQL的小区物业管理系统

系统背景 在当今信息化高速发展的时代&#xff0c;小区物业管理正经历着从传统模式向智能化、高效化转型的深刻变革。这一转变的核心驱动力&#xff0c;正是小区物业管理系统的全面智能化升级。该系统不仅极大地提升了物业管理的效率与精确度&#xff0c;还深刻重塑了物业与业主…

数分基础(03-1)客户特征分析

文章目录 客户特征分析1. 数据集2. 思路与步骤2.1 特征工程2.2 识别方法2.3 可视化 3. 分析准备3.1 读取数据集3.2 识别不同客户群体3.2.1 使用K-Means聚类进行初步细分3.2.2 关于聚类方法&#xff08;1&#xff09;特征缩放1&#xff09;平衡特征对模型的影响力&#xff0c;避…

通过ICMP判断网络故障

一、ICMP协议 Internet控制消息协议ICMP(Internet Control Message Protocol)是IP协议的辅助协议。 ICMP协议用来在网络设备间传递各种差错和控制信息&#xff0c;对于收集各种网络信息、诊断和排除各种网络故障等方面起着至关重要的作用。 TypeCode描述备注00Echo Replyping…

C++从入门到起飞之——list使用 全方位剖析!

​ &#x1f308;个人主页&#xff1a;秋风起&#xff0c;再归来~&#x1f525;系列专栏&#xff1a;C从入门到起飞 &#x1f516;克心守己&#xff0c;律己则安 目录 1、迭代器 2、push_back与emplace_back 3、list成员函数sort与库sort比较 4、merge 5、uniqu…

2024117读书笔记|《李煜词(果麦经典)》——一壶酒,一竿身,快活如侬有几人?一片芳心千万绪,人间没个安排处

2024117读书笔记|《李煜词&#xff08;果麦经典&#xff09;》——一壶酒&#xff0c;一竿身&#xff0c;快活如侬有几人&#xff1f;一片芳心千万绪&#xff0c;人间没个安排处 《李煜词&#xff08;果麦经典&#xff09;》李煜的词很美&#xff0c;插图也不错&#xff0c;很值…

基于粒子群优化算法的六自由度机械臂三维空间避障规划

摘要&#xff1a;本研究旨在解决机械臂在复杂环境中避障路径规划的问题。本文提出了一种利用粒子群优化算法&#xff08;PSO&#xff09;进行机械臂避障规划的方法&#xff0c;通过建立机械臂的运动模型&#xff0c;将避障问题转化为优化问题。PSO算法通过模拟群体中个体的社会…

ggml 简介

ggml是一个用 C 和 C 编写、专注于 Transformer 架构模型推理的机器学习库。该项目完全开源&#xff0c;处于活跃的开发阶段&#xff0c;开发社区也在不断壮大。ggml 和 PyTorch、TensorFlow 等机器学习库比较相似&#xff0c;但由于目前处于开发的早期阶段&#xff0c;一些底层…

8月28c++

c手动封装顺序表 #include <iostream>using namespace std; using datatype int;//类型重命名struct SeqList { private:datatype *data;//顺序表数组int size0;//数组大小int len0;//顺序表实际长度 public:void init(int s);//初始化函数bool empty();//判空函数bool …

python有主函数吗

python和C/Java不一样&#xff0c;没有主函数一说&#xff0c;也就是说python语句执行不是从所谓的主函数main开始的。 当运行单个python文件时&#xff0c;如运行a.py&#xff0c;这个时候a的一个属性__name__是__main__。 当调用某个python文件时&#xff0c;如b.py调用a.p…

HDD介绍

HDD是“Hard Disk Drive”的缩写&#xff0c;意为“硬盘驱动器”&#xff0c;是计算机中用于存储数据和程序的主要设备之一。 硬盘有机械硬盘(Hard Disk Drive&#xff0c;HDD)和固态硬盘(SSD)之分。机械硬盘即是传统普通硬盘&#xff0c;主要由&#xff1a;盘片&#xff0c;磁…