【Linux系统编程】第四十七弹---深入探索:POSIX信号量与基于环形队列的生产消费模型实现

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】

目录

1、POSIX信号量

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

2.1、代码实现

2.1.1、RingQueue基本结构

2.1.2、PV操作

2.1.3、构造析构函数

2.1.4、生产者入队

2.1.5、消费者出队

2.2、代码测试

2.2.1、内置类型

2.2.2、类类型 

2.2.3、多生产多消费


1、POSIX信号量

POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步

初始化信号量

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);

参数:
    pshared:0表示线程间共享,非零表示进程间共享
    value:信号量初始值

销毁信号量

int sem_destroy(sem_t *sem);

等待信号量

功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()

发布信号量

功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);//V()

上一弹生产者-消费者的例子是基于queue的,其空间可以动态分配,现在基于固定大小的环形队列重写这个程序(POSIX信号量):

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

  • 环形队列采用数组模拟,用模运算来模拟环状特性

  • 环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或者标记位来判断满或者空。另外也可以预留一个空的位置,作为满的状态 

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

 多线程如何在环形队列中进行生产和消费?1.单生产单消费 2.多生产多消费

1.队列为空,让谁先访问?生产者先生产
2.队列为满,让谁先访问?消费者来消费
3.队列不为空 && 队列不为满 -- 生产和消费同时进行

2.1、代码实现

2.1.1、RingQueue基本结构

RingQueue使用模板类实现,当单生产单消费的时候可以不加锁,因为使用信号量就可以实现同步与互斥,当多生产多消费的时候需要进行加锁,且为两把锁!!!

template<typename T>
class RingQueue
{
private:
    // 等待信号量,信号量-1
    void P(sem_t& s);
    // 发布信号量(归还资源),信号量+1
    void V(sem_t& s);
public:
    // 构造函数初始化队列大小,信号量以及互斥锁
    RingQueue(int max_cap)
        :_ringqueue(max_cap)/*构造队列成员个数*/,_max_cap(max_cap),_c_step(0),_p_step(0);
    // 生产者生产数据
    void Push(const T& in);
    // 消费者使用数据
    void Pop(T* out);
    // 析构函数释放信号量和互斥锁
    ~RingQueue();
private:
    std::vector<T> _ringqueue; // vector实现队列
    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;
};

2.1.2、PV操作

P操作等待信号量,V操作发布信号量!

// 等待信号量,信号量-1
void P(sem_t& s)
{
    sem_wait(&s);
}
// 发布信号量(归还资源),信号量+1
void V(sem_t& s)
{
    sem_post(&s);
}

2.1.3、构造析构函数

构造函数申请队列空间,初始化信号量和互斥锁(多生产多消费需要使用互斥锁),析构函数释放信号量和互斥锁!!!

// 构造函数初始化队列大小,信号量以及互斥锁
RingQueue(int max_cap)
    :_ringqueue(max_cap)/*构造队列成员个数*/,_max_cap(max_cap),_c_step(0),_p_step(0)
{
    sem_init(&_data_sem,0,0); // 第一个0表示线程共享,第二个0表示信号量初始值为0
    sem_init(&_space_sem,0,max_cap);

    // pthread_mutex_init(&_c_mutex,nullptr);
    // pthread_mutex_init(&_p_mutex,nullptr);
}
// 析构函数释放信号量和互斥锁
~RingQueue()
{
    sem_destroy(&_data_sem);
    sem_destroy(&_space_sem);

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

2.1.4、生产者入队

生产者生产数据(无锁版本)!!!

// 生产者生产数据
void Push(const T& in)
{
    // 信号量:是一个计数器,是资源的预订机制。预订:在外部,可以不判断资源是否满足,就可以知道内部资源的情况!
    P(_space_sem); // 信号量这里,对资源进行使用,申请,为什么不判断一下条件是否满足???信号量本身就是判断条件!
    
    _ringqueue[_p_step] = in; // 生产数据
    _p_step++;
    _p_step %= _max_cap; // 循环轮转

    V(_data_sem); // 归还数据资源,不为满归还
}

2.1.5、消费者出队

消费者使用数据(无锁版本)!!!

// 消费者使用数据
void Pop(T* out)
{
    P(_data_sem); 

    *out = _ringqueue[_c_step];
    _c_step++;
    _c_step %= _max_cap;

    V(_space_sem); 
}

2.2、代码测试

2.2.1、内置类型

Consumer

void *Consumer(void *args)
{
    RingQueue<int> *rq = static_cast<RingQueue<int> *>(args);
    while (true)
    {
        // 1.消费
        int data;
        rq->Pop(&data);
        // 2.处理数据
        std::cout << "Consumer->" << data << std::endl;
    }
}

Productor

void *Productor(void *args)
{
    srand(time(nullptr) ^ getpid());
    RingQueue<int> *rq = static_cast<RingQueue<int> *>(args);
    while (true)
    {
        // 1.构造数据
        int x = rand() % 10 + 1; // [1,10]
        // 2.生产数据
        rq->Push(x);
        std::cout << "Productor->" << x << std::endl;
        sleep(1);
    }
}

主函数

int main()
{
    RingQueue<int> *rq = new RingQueue<int>(5);
    
    // 单生产,单消费
    pthread_t c, p;
    pthread_create(&c, nullptr, Consumer, rq);
    pthread_create(&p, nullptr, Productor, rq);

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

运行结果 

2.2.2、类类型 

Consumer

void *Consumer(void *args)
{
    RingQueue<Task> *rq = static_cast<RingQueue<Task>*>(args);
    while (true)
    {
        // 1.消费
        Task t;
        rq->Pop(&t);
        // 2.处理数据
        t();
        std::cout << "Consumer->" << t.result() << std::endl;
    }
}

Productor

void *Productor(void *args)
{
    srand(time(nullptr) ^ getpid());
    RingQueue<Task> *rq = static_cast<RingQueue<Task> *>(args);
    while (true)
    {
        // 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;
        sleep(1);
    }
}

主函数

int main()
{
    RingQueue<Task> *rq = new RingQueue<Task>(5);
    
    // 单生产,单消费
    pthread_t c, p;
    pthread_create(&c, nullptr, Consumer, rq);
    pthread_create(&p, nullptr, Productor, rq);

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

运行结果  

2.2.3、多生产多消费

多生产多消费能够保证生产与消费之间的同步与互斥,但是不能保证生产与生产,消费与消费之间的同步与互斥,因此需要在入队与出队时加锁!!!

生产者入队

// 生产者生产数据
void Push(const T& in)
{
    // 信号量:是一个计数器,是资源的预订机制。预订:在外部,可以不判断资源是否满足,就可以知道内部资源的情况!
    P(_space_sem); // 信号量这里,对资源进行使用,申请,为什么不判断一下条件是否满足???信号量本身就是判断条件!
    
    pthread_mutex_lock(&_p_mutex); // 给生产者上锁,解决多生产多消费数据不一致问题,放在P后效率更高
    _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); 
}

主函数

int main()
{
    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;
}

无锁

加锁

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

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

相关文章

除了 TON, 哪些公链在争夺 Telegram 用户?数据表现如何?

作者&#xff1a;Stella L (stellafootprint.network) 在 2024 年&#xff0c;区块链游戏大规模采用迎来了一个意想不到的催化剂&#xff1a;Telegram。随着各大公链争相布局这个拥有海量用户基础的即时通讯平台&#xff0c;一个核心问题浮出水面&#xff1a;这种用户获取策略…

小白进!QMK 键盘新手入门指南

经常玩键盘的伙伴应该都知道&#xff0c;现在的键盘市场可谓是百花齐放&#xff0c;已经不是之前的单一功能产品化时代。我们可以看到很多诸如&#xff1a;机械轴键盘、磁轴键盘、光轴键盘、电感轴键盘&#xff0c;以及可能会上市的光磁轴键盘&#xff0c;更有支持屏幕的、带旋…

【HarmonyOS】鸿蒙系统在租房项目中的项目实战(二)

从今天开始&#xff0c;博主将开设一门新的专栏用来讲解市面上比较热门的技术 “鸿蒙开发”&#xff0c;对于刚接触这项技术的小伙伴在学习鸿蒙开发之前&#xff0c;有必要先了解一下鸿蒙&#xff0c;从你的角度来讲&#xff0c;你认为什么是鸿蒙呢&#xff1f;它出现的意义又是…

《Markdown语法入门》

文章目录 《Markdown语法入门》1.标题2.段落2.1 换行2.2分割线 3.文字显示3.1 字体3.2 上下标 4. 列表4.1无序列表4.2 有序列表4.3 任务列表 5. 区块显示6. 代码显示6.1 行内代码6.2 代码块 7.插入超链接8.插入图片9. 插入表格 《Markdown语法入门》 【Typora 教程】手把手教你…

北京大学c++程序设计听课笔记101

基本概念 程序运行期间&#xff0c;每个函数都会占用一段连续的内存空间。而函数名就是该函数所占内存区域的起始地址&#xff08;也称“入口地址”&#xff09;。我们可以将函数的入口地址赋给一个指针变量&#xff0c;使该指针变量指向该函数。然后通过指针变量就可以调用这个…

C++:boost库安装

官网&#xff1a;https://www.boost.org/ Boost 库在 C 社区中广受欢迎&#xff0c;主要因为它提供了丰富、强大且稳定的功能&#xff0c;可以显著提高开发效率和代码质量。下面是使用 Boost 库的主要优势和特点&#xff1a; 1. 丰富的功能集合 Boost 提供了数十个高质量的 …

VScode学习前端-01

小问题合集&#xff1a; vscode按&#xff01;有时候没反应&#xff0c;有时候出来&#xff0c;是因为------>必须在英文状态下输入&#xff01; 把鼠标放在函数、变量等上面&#xff0c;会自动弹出提示&#xff0c;但挡住视线&#xff0c;有点不习惯。 打开file->pre…

机房动环境监控用各种列表已经淘汰了,现在都是可视化图表展示了

在信息技术飞速发展的今天&#xff0c;机房作为数据存储、处理和传输的核心场所&#xff0c;其稳定运行至关重要。过去&#xff0c;机房动环境监控主要依赖各种列表形式来呈现数据&#xff0c;但如今&#xff0c;这种方式已经逐渐被淘汰&#xff0c;取而代之的是更加直观、高效…

Pytest-Bdd-Playwright 系列教程(10):配置功能文件路径 优化场景定义

Pytest-Bdd-Playwright 系列教程&#xff08;10&#xff09;&#xff1a;配置功能文件路径 & 优化场景定义 前言一、功能文件路径的配置1.1 全局设置功能文件路径1.2. 在场景中覆盖路径 二、避免重复输入功能文件名2.1 使用方法2.2 functools.partial 的背景 三、应用场景总…

【软件测试】自动化常用函数

文章目录 元素的定位cssSelectorxpath查找元素 操作测试对象点击/提交对象——click()模拟按键输入——sendKeys(“”)清除文本内容——clear()获取文本信息——getText()获取页面标题和 URL 窗口设置窗口大小切换窗口关闭窗口 等待强制等待隐式等待显式等待 浏览器导航 元素的…

CC4学习记录

&#x1f338; CC4 CC4要求的commons-collections的版本是4.0的大版本。 其实后半条链是和cc3一样的&#xff0c;但是前面由于commons-collections进行了大的升级&#xff0c;所以出现了新的前半段链子。 配置文件&#xff1a; <dependency><groupId>org.apach…

【linux】网络基础 ---- 数据链路层

用于两个设备(同一种数据链路节点)之间进行传递 数据链路层解决的问题是&#xff1a;直接相连的主机之间&#xff0c;进行数据交付 1. 认识以太网 "以太网" 不是一种具体的网络, 而是一种技术标准&#xff1a; 既包含了数据链路层的内容, 也包含了一些物理层的内容…

5. ARM_指令集

概述 分类 汇编中的符号&#xff1a; 指令&#xff1a;能够编译生成一条32位机器码&#xff0c;并且能被处理器识别和执行伪指令&#xff1a;本身不是指令&#xff0c;编译器可以将其替换成若干条指令伪操作&#xff1a;不会生成指令&#xff0c;只是在编译阶段告诉编译器怎…

小程序租赁系统开发为企业提供高效便捷的租赁服务解决方案

内容概要 在这个数字化飞速发展的时代&#xff0c;小程序租赁系统应运而生&#xff0c;成为企业管理租赁业务的一种新选择。随着移动互联网的普及&#xff0c;越来越多的企业开始关注如何利用小程序来提高租赁服务的效率和便捷性。小程序不仅可以为用户提供一个快速、易用的平…

计算机组成原理——高速缓存

标记表示——主存块号和缓存块之前的一一对应关系

赛元免费开发板申请

在作者网上冲浪的时候&#xff0c;突然发现了一个国内的良心企业&#xff0c;虽然现在不是很有名&#xff0c;但是他现在是有一个样品申请的活动&#xff0c;他就是国内的Redfine新定义&#xff0c;他申请的板子是用的赛元MCU&#xff0c;作者本着有板子就要申请的原则&#xf…

Ubuntu 的 ROS 操作系统 turtlebot3 SLAM仿真

引言 SLAM&#xff08;同步定位与地图构建&#xff09;在Gazebo仿真环境中的应用能够模拟真实机器人进行环境建图和导航。通过SLAM仿真&#xff0c;开发者可以在虚拟环境中测试算法&#xff0c;而不必依赖真实硬件&#xff0c;便于调试与优化。 Gazebo提供了多个虚拟环境&…

【解决】Layout 下创建槽位后,执行 Image 同步槽位位置后表现错误的问题。

开发平台&#xff1a;Unity 6.0 编程语言&#xff1a;CSharp 编程平台&#xff1a;Visual Studio 2022   一、问题背景 | 开发库存系统 图1 位置同步失败问题 图2 位置正常同步效果表现 黑框 作用于 UnityEngine.UI.GridLayoutGruop&#xff0c;形成 4x6 布局&#xff0c;如…

红日靶场-1详细解析(适合小白版)

红日靶场涉及内网知识&#xff0c;和前期靶场不太一样&#xff0c;前期靶场大部分都是单个靶机获得root权限&#xff0c;而这一次更综合&#xff0c;后期也会继续学习内网知识&#xff0c;继续打红日靶场&#xff0c;提高自己的综合技能。 环境搭建 首先本题的网络拓扑结构如…

LabVIEW大数据处理

在物联网、工业4.0和科学实验中&#xff0c;大数据处理需求逐年上升。LabVIEW作为一款图形化编程语言&#xff0c;凭借其强大的数据采集和分析能力&#xff0c;广泛应用于实时数据处理和控制系统中。然而&#xff0c;在面对大数据处理时&#xff0c;LabVIEW也存在一些注意事项。…