Sparrow系列拓展篇:对信号量应用问题的深入讨论
前言
笔者之前已经介绍过了Sparrow信号量的源码,但是对于信号量的使用,并没有讲得非常详细,仅仅讲了同步与互斥的概念。
本章让笔者介绍如何使用Sparrow的信号量,深入探讨一下信号量在同步、计数与互斥中的应用。
使用信号量解决资源问题
生产者,消费者是一种经典模型,一边是供给,一边是需求。在计算机历史上,有许多这种问题,例如哲学家进餐问题,有兴趣的读者可以自行研究。
本篇文章中,我们主要解决以下问题:
单生产者,单消费者
多生产者,单消费者
多生产者,多消费者
多读者,多写者
单生产者,单消费者
我们通过创建环形缓冲区(一种特殊的队列)来建立生产者和消费者模型,一个插入消息,另一个获取消息。
观察结构体,有两个信号量,一个初始化时用于同步,一个用于计数。
只要分配合理的优先级,信号量可以用于互斥,与互斥锁相比,它的优点在于没有所有者的概念,完全可以让两个不同的线程形成原子操作。
看看具体的代码:
Oo_insert函数是生产者:先使用计数信号量获取是否有空间,然后插入数据,然后释放信号通知消费者。
Oo_remove函数是消费者:先获取是否有数据,再取出数据,然后恢复计数。
#define BufferSIZE 5
Class(Oo_buffer)
{int buf[BufferSIZE];int in;int out;Semaphore_Handle item;Semaphore_Handle space;
};Oo_buffer_handle Oo_buffer_creat(void)
{Oo_buffer_handle Oo_buffer1 = heap_malloc(sizeof (Oo_buffer));*Oo_buffer1 = (Oo_buffer){.buf = {0,0,0,0,0},.in = 0,.out = 0,.item = semaphore_creat(0),.space = semaphore_creat(BufferSIZE)};return Oo_buffer1;
}void Oo_insert(Oo_buffer_handle Oo_buffer1, int object)
{semaphore_take(Oo_buffer1->space, 1);Oo_buffer1->buf[Oo_buffer1->in] = object;Oo_buffer1->in = (Oo_buffer1->in + 1) % BufferSIZE;semaphore_release(Oo_buffer1->item);
}int Oo_remove(Oo_buffer_handle Oo_buffer1)
{semaphore_take(Oo_buffer1->item, 1);int item1 = Oo_buffer1->buf[Oo_buffer1->out];Oo_buffer1->out = (Oo_buffer1->out + 1) % BufferSIZE;semaphore_release(Oo_buffer1->space);return item1;
}
多生产者,单消费者
多生产者时,与单生产者相比只有一个地方需要注意,那就是对环形缓冲区本身的访问,如果仍然使用单生产者的模型,多个生产者可以同时获取计数信号量,但是它们的下标操作会导致竞态的发生。
所以我们额外需要一个信号量,用于保证生产者们互斥访问环形缓冲区。
Class(Mo_buffer)
{int buf[BufferSIZE];int in;int out;Semaphore_Handle item;Semaphore_Handle space;Semaphore_Handle guard;
};Mo_buffer_handle Mo_buffer_creat(void)
{Mo_buffer_handle Mo_buffer1 = heap_malloc(sizeof (Mo_buffer));*Mo_buffer1 = (Mo_buffer){.buf = {0,0,0,0,0},.in = 0,.out = 0,.item = semaphore_creat(0),.space = semaphore_creat(BufferSIZE),.guard = semaphore_creat(1)};return Mo_buffer1;
}void Mo_insert(Mo_buffer_handle Mo_buffer1, int object)
{semaphore_take(Mo_buffer1->space, 1);semaphore_take(Mo_buffer1->guard, 1),Mo_buffer1->buf[Mo_buffer1->in] = object;Mo_buffer1->in = (Mo_buffer1->in + 1) % BufferSIZE;semaphore_release(Mo_buffer1->guard);semaphore_release(Mo_buffer1->item);
}int Mo_remove(Mo_buffer_handle Mo_buffer1)
{semaphore_take(Mo_buffer1->item, 1);int item1 = Mo_buffer1->buf[Mo_buffer1->out];Mo_buffer1->out = (Mo_buffer1->out + 1) % BufferSIZE;semaphore_release(Mo_buffer1->space);return item1;
}
多生产者,多消费者
多消费者同理,我们额外需要一个信号量,用于保证消费者们互斥访问环形缓冲区。
Class(Mm_buffer)
{int buf[BufferSIZE];int in;int out;Semaphore_Handle item;Semaphore_Handle space;Semaphore_Handle guard;
};Mm_buffer_handle Mm_buffer_creat(void)
{Mm_buffer_handle Mm_buffer1 = heap_malloc(sizeof (Mm_buffer));*Mm_buffer1 = (Mm_buffer){.buf = {0,0,0,0,0},.in = 0,.out = 0,.item = semaphore_creat(0),.space = semaphore_creat(BufferSIZE),.guard = semaphore_creat(1),};return Mm_buffer1;
}void Mm_insert(Mm_buffer_handle Mm_buffer1, int object)
{semaphore_take(Mm_buffer1->space, 1);semaphore_take(Mm_buffer1->guard, 1),Mm_buffer1->buf[Mm_buffer1->in] = object;Mm_buffer1->in = (Mm_buffer1->in + 1) % BufferSIZE;semaphore_release(Mm_buffer1->guard);semaphore_release(Mm_buffer1->item);
}int Mm_remove(Mm_buffer_handle Mm_buffer1)
{semaphore_take(Mm_buffer1->item, 1);semaphore_take(Mm_buffer1->guard, 1);int item1 = Mm_buffer1->buf[Mm_buffer1->out];Mm_buffer1->out = (Mm_buffer1->out + 1) % BufferSIZE;semaphore_release(Mm_buffer1->guard);semaphore_release(Mm_buffer1->space);return item1;
}
多读者,多写者
多读多写问题稍微复杂一些,我们需要两个互斥信号量,两个同步信号量,两个读者计数值,两个写者计数值。
顺便一提,笔者这里讲的互斥信号量不是互斥锁。Sparrow中互斥信号量是初始化时value为1的信号量,它的优点在于没有所有者的概念。
读模型:
申请读:先声明为读者,获取互斥信号量保证对计数值的互斥,如果没有活跃的写者,那么获取访问权限,否则等待读取信号量。
读完释放:减少计数值,如果没有正在读的读者,但是存在等待的写者,那么唤醒它们。
写模型:
申请写:先声明为写者,如果没有正在读的读者,那么成为写者,然后获取互斥信号量保证申请写以及写完释放操作的原子性。
写完释放:获取互斥信号量,减少计数,如果没有其他活跃者,但是存在等待的读者,那么增加正在读的读者的计数,然后唤醒这些读者。
Class(MrMw_control)
{Semaphore_Handle read;Semaphore_Handle write;Semaphore_Handle W_guard;Semaphore_Handle C_guard;int active_reader;int reading_reader;int active_writer;int writing_writer;
};MrMw_control_handle MrOw_creat(void)
{MrMw_control_handle MrMw_control1 = heap_malloc(sizeof (MrMw_control));*MrMw_control1 = (MrMw_control){.read = semaphore_creat(0),.write = semaphore_creat(0),.W_guard = semaphore_creat(1),.C_guard = semaphore_creat(1),.active_reader = 0,.reading_reader = 0,.active_writer = 0,.writing_writer = 0};return MrMw_control1;
}void read_acquire(MrMw_control_handle MrMw_control1)
{semaphore_take(MrMw_control1->C_guard, 1);MrMw_control1->active_reader += 1;if(MrMw_control1->active_writer == 0){MrMw_control1->reading_reader += 1;semaphore_release(MrMw_control1->read);}semaphore_release(MrMw_control1->C_guard);semaphore_take(MrMw_control1->read, 1);
}void read_release(MrMw_control_handle MrMw_control1)
{semaphore_take(MrMw_control1->C_guard, 1);MrMw_control1->reading_reader -= 1;MrMw_control1->active_reader -= 1;if(MrMw_control1->reading_reader == 0){while(MrMw_control1->writing_writer < MrMw_control1->active_writer){MrMw_control1->writing_writer += 1;semaphore_release(MrMw_control1->write);}}semaphore_release(MrMw_control1->C_guard);
}void write_acquire(MrMw_control_handle MrMw_control1)
{semaphore_take(MrMw_control1->C_guard, 1);MrMw_control1->active_writer -= 1;if(MrMw_control1->reading_reader == 0){MrMw_control1->writing_writer += 1;semaphore_release(MrMw_control1->write);}semaphore_release(MrMw_control1->C_guard);semaphore_take(MrMw_control1->write, 1);semaphore_take(MrMw_control1->W_guard, 1);
}void write_release(MrMw_control_handle MrMw_control1)
{semaphore_release(MrMw_control1->W_guard);semaphore_take(MrMw_control1->C_guard, 1);MrMw_control1->writing_writer -= 1;MrMw_control1->active_writer -= 1;if(MrMw_control1->active_writer == 0){while(MrMw_control1->reading_reader < MrMw_control1->active_reader){MrMw_control1->reading_reader += 1;semaphore_release(MrMw_control1->read);}}semaphore_release(MrMw_control1->C_guard);
}
总结
笔者讲解了如何使用信号量解决生产者和消费者、多读多写者问题。
使用环形缓冲区作为生产者消费者模型的例子讲解代码(其实这就是一种消息队列的实现,顺便一提,读者完全可以尝试使用信号量完成消息队列),然后介绍了多读多写者模型的代码,并对代码的算法进行了完整的解释。
源码在Sparrow文件夹的demo里面:skaiui2/SKRTOS_sparrow at source
相关文章:
Sparrow系列拓展篇:对信号量应用问题的深入讨论
前言 笔者之前已经介绍过了Sparrow信号量的源码,但是对于信号量的使用,并没有讲得非常详细,仅仅讲了同步与互斥的概念。 本章让笔者介绍如何使用Sparrow的信号量,深入探讨一下信号量在同步、计数与互斥中的应用。 使用信号量解…...
图文详解Docker下配置、测试Redis
文章目录 前言实测环境:实验思路: 正文1.准备工作2. 配置、运行 Redis 容器3. 配置测试 总结 前言 配置、测试redis数据库服务器,首先确保正确安装docker,并且已启动运行,具体安装docker方法见笔者前面的博文《OpenEu…...
Python编程艺术:优雅与实用的完美平衡(推导式)
在Python这门优雅的编程语言中,处处体现着"简洁即是美"的设计哲学。今天我们深入探讨Python中那些让代码更优雅、更高效的编程技巧,这些技巧不仅能提升代码的可读性,还能让编程过程充满乐趣。 列表推导式的魔力 Python的列表推导…...
Spring Boot框架Starter组件整理
在Spring Boot框架中,starter是一种预定义的依赖集合,旨在简化Maven或Gradle等构建工具中的依赖管理。每个starter都包含了实现特定功能所需的库和组件,以及相应的配置文件。开发者只需在项目中引入相应的starter依赖,即可快速搭建…...
C/C++基础知识复习(27)
1) 移动语义和拷贝语义的区别 拷贝语义和移动语义是C中对象所有权管理的两种机制,主要在对象初始化、赋值或传参时体现。 拷贝语义 (Copy Semantics) 行为:通过深拷贝或浅拷贝,创建一个新对象,并将原对象的值或资源复制到新对象…...
IEC61850实现方案和测试-2
IEC61850实现方案和测试-1作为介绍实现方案和测试的第二篇文章,后续会继续更新,欢迎关注。 第一篇是:IEC61850实现方案和测试-1-CSDN博客 UCA详细测试用例下载: 链接: https://pan.baidu.com/s/1TTMlYRfzKITgrkWwwtcrDg 提取码:…...
flume-将日志采集到hdfs
看到hdfs大家应该做什么? 是的你应该去把集群打开, cd /export/servers/hadoop/sbin 启动集群 ./start-all.sh 在虚拟机hadoop02和hadoop03上的conf目录下配置相同的日志采集方案,‘ cd /export/servers/flume/conf 切换完成之后&#…...
一文学习开源框架LeakCanary
LeakCanary 简介 LeakCanary 是一个由 Square 开发的开源工具,主要用于检测和诊断 Android 应用中的内存泄漏问题。它通过自动化的方式帮助开发者捕捉和分析可能导致内存泄漏的对象,简化了内存问题的排查过程。 LeakCanary 的功能 自动检测内存泄漏&a…...
jetson orin系列开发版安装cuda的gpu版本的opencv
opencv安装包下载地址: https://github.com/opencv/opencv/扩展库下载地址: https://github.com/opencv/opencv_contrib1. 删除jetpack包中的opencv版本 原先的opencv库安装在目录/usr/lib/aarch64-linux-gnu/下(一般其他的第三方库也都安…...
数据结构-8.Java. 七大排序算法(中篇)
本篇博客给大家带来的是排序的知识点, 由于时间有限, 分两天来写, 中篇主要实现后三种排序算法: 冒泡排序,快速排序,下一篇讲 归并排序. 文章专栏: Java-数据结构 若有问题 评论区见 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作…...
数据结构C语言描述4(图文结合)--栈的实现,中序转后序表达式的实现
前言 这个专栏将会用纯C实现常用的数据结构和简单的算法;有C基础即可跟着学习,代码均可运行;准备考研的也可跟着写,个人感觉,如果时间充裕,手写一遍比看书、刷题管用很多,这也是本人采用纯C语言…...
python基本数据类型 -- 元组tuple
在 Python 中,元组(Tuple)是一种轻量级的、不可变的数据结构。与列表类似,元组用于存储有序的数据集合,但它一旦创建,其内容就无法更改。这种特性让元组在某些场景下更加安全和高效。本文将从定义、操作、应…...
tcpdump交叉编译
TCPDUMP在Libpcap上开发。 首先需要编译libcap。 网上那么多教程,下载地址都只给了一个英文的官网首页, 你尽可以试试,从里面找到下载地址都要费半天时间。 \color{red}网上那么多教程,下载地址都只给了一个英文的官网首页&#…...
Spring IOC注入方式、Bean作用域
Spring IOC注入 手动注入 set方法注入 需要提供set方法 public class UserService {private UserDao userDao; public void setUserDao(UserDao userDao) {this.userDao userDao;} } 设置属性字段的值 <bean id"userService" class"com.shsxt.servi…...
uniapp微信小程序转发跳转指定页面
onShareAppMessage 是微信小程序中的一个重要函数,用于自定义转发内容。当用户点击右上角的菜单按钮,并选择“转发”时,会触发这个函数。开发者可以在这个函数中返回一个对象,用于定义分享卡片的标题、图片、路径等信息。 使用场…...
利用uniapp开发鸿蒙:运行到鸿蒙模拟器—踩坑合集
从uniapp运行到鸿蒙模拟器上这一步,就有非常多的坑,一些常见的坑,官网都有介绍,就不再拿出来了,这里记录一下官网未记录的大坑 1.运行路径从hbuilderx启动鸿蒙模拟器 解决方法: Windows系统,官…...
【Vue】Vue3.0(二十五)Vue3.0中的具名插槽 的概念和使用场景
上篇文章 【Vue】Vue3.0(二十四)Vue3.0中 r e f s 、 refs 、 refs、parent 的概念和使用场景 🏡作者主页:点击! 🤖Vue专栏:点击! ⏰️创作时间:2024年11月20日16点30分 …...
【pytorch-02】:张量的索引、形状操作和常见运算函数
文章目录 1 张量索引1.1 简单行列索引和列表索引1.2 布尔索引和多维索引 2 张量的形状操作2.1 reshape函数2.2 transpose和permute函数的使用2.3 view和contiguous函数2.4 squeeze和unsqueeze函数用法2.5 张量更改形状小结 3 常见运算函数 1 张量索引 1.1 简单行列索引和列表索…...
C语言-指针作为函数返回值及二级指针
1、指针作为函数返回值 c语言允许函数的返回值是一个指针(地址)我们将这样的函数称为指针函数,下面的例子定义一了一个函数strlong(),用来返回两个字符串中较长的一个: 1. #include <stdio…...
css 使用图片作为元素边框
先看原始图片 再看效果 边框的四个角灭有拉伸变形,但是图片的中部是拉伸的 代码 border-style: solid;/* 设置边框图像的来源 */border-image-source: url(/static/images/mmwz/index/bk_hd3x.png);/* 设置如何切割图像 */border-image-slice: 66;/* 设置边框的宽度 */border…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
从面试角度回答Android中ContentProvider启动原理
Android中ContentProvider原理的面试角度解析,分为已启动和未启动两种场景: 一、ContentProvider已启动的情况 1. 核心流程 触发条件:当其他组件(如Activity、Service)通过ContentR…...
【LeetCode】算法详解#6 ---除自身以外数组的乘积
1.题目介绍 给定一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O…...
vue3 daterange正则踩坑
<el-form-item label"空置时间" prop"vacantTime"> <el-date-picker v-model"form.vacantTime" type"daterange" start-placeholder"开始日期" end-placeholder"结束日期" clearable :editable"fal…...
