当前位置: 首页 > article >正文

linux多线(进)程编程——(9)信号量(一)

前言

在找到了共享内存存在的问题后,进程君父子着手开始解决这些问题。他们发明了一个新的神通——信号量。

信号量

信号量是一个计数器,用于管理对共享资源的访问权限。主要特点包括:
(1)是一个非负整数
(2)提供两种原子操作:P(等待)和V(释放)
(3)可以用于进程同步和互斥
(4)用于显示资源的数量
如何理解这些功能呢,他和普通的变量实现的计数器的区别是什么呢?请看下面的例子:

普通变量实现进程间共享资源计数的问题

假设有一个停车场,内部有若干停车位。当前剩余的停车位数量会显示在大门前,当进入一辆车时,大门打开,确保车辆进入后,大门合上,剩余停车位减1。随着规模的扩建,现在停车场增加了一个大门,共有东西两个大门。假设当前停车场有一个空位,如下图所示:
在这里插入图片描述
如图,剩余车位为1,有两辆车要进入我们的停车场。由于停车场的传感器机制要等到汽车确定进入停车场并关闭大门后,才会重新计算剩余停车场数量,对于图中情况,两个车都靠近停车场,将会出现下面的情况:
在这里插入图片描述
上图可以看到两辆车几乎同时进入停车场,且剩余车位仍然显示为1,因为大门没有及时合上。等到两辆车都进入停车场后,稍微晚一点进入的车辆发现了现在车场里面已经没有空位了,如下图所示。
在这里插入图片描述
这里停车场出现问题的主要原因在哪里呢?就是他加装了一个大门。我们现在来把停车场给抽象出来,如下表:

例子中的元素计算机
单个大门的停车场普通虚拟内存
单个大门单个进程
多个大门的停车场共享内存
多个大门多个进程
剩余车位显示屏普通变量实现的计数器

为了防止同时写入造成冲突,我们约束同一时刻只能有一个进程访问共享内存,一个进程进入后,表示共享资源个数的计数器会从1变成0。上节课我们讲过,对于普通变量实现的计数器的增减操作是非原子操作,会被拆分成3个机器指令。当两个进程同时想要访问共享内存时,如果时间刚刚好相差不大,就会引起两个进程同时获得计数器值为1的情况。就类似于上面的例子中两辆车几乎同时进入停车场。

信号量的使用

我们再来看一下信号量的优点:
信号量是一个计数器,用于管理对共享资源的访问权限。主要特点包括:
(1)是一个非负整数
(2)提供两种原子操作:P(等待:值减1)和V(释放:值加1)
(3)可以用于进程同步和互斥
(4)用于显示资源的数量
看到这里大家应该清楚了,信号量就是用来在进程间计数与同步的,那么让我来看看如何使用它们吧:

创建或获取信号量集合:

int semget(key_t key, int nsems, int semflg);

这些参数真的很多都是老朋友了,包括这些函数的命名方法,当然还是要介绍一下:
key:键值
nsems:表示在这个集合中创建多少个信号量
semflag:状态位
ret:返回值为信号量集合的ID

使用方法如下面代码块所示:

int main() {int semid = semget((key_t)1, 1, 0666 | IPC_CREAT);if (semid == -1) {printf("sem create fail\n");return 0;}printf("the id of sem is %d\n", id);return 0;
}

编译后运行程序,输出

lol@hyl:~/work/linux_study/sem$ gcc -o p1 proc1.c 
lol@hyl:~/work/linux_study/sem$ ./p1
the id of sem is 1

运行ipcs指令可以看到:

lol@hyl:~/work/linux_study/sem$ ipcs------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    ------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      ------ Semaphore Arrays --------
key        semid      owner      perms      nsems     
0x00000001 1          hyl        666        1      

到这里,我们ipcs指令下的三大元老:共享内存,消息队列,信号量就全部和大家见面了。同样,这个信号量是不依赖于进程的,属于操作系统的,因此我们发现主函数执行完成后我们的信号量仍然存在。为了防止发生资源泄露,我们需要删除这个信号量,输入:ipcrm -s 1,这里-s表示信号量的意思。从输出信息中我们可以发现信号量被删除了。

lol@hyl:~/work/linux_study/sem$ ipcrm -s 1
lol@hyl:~/work/linux_study/sem$ ipcs------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    ------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      ------ Semaphore Arrays --------
key        semid      owner      perms      nsems    

信号量操作:

int semop(int semid, struct sembuf *sops, unsigned int nsops);

semid:信号量集合的ID
nsops:需要操作的信号量的个数,当集合内信号量数量大于1有效,一般另其为1
sopsstruct sembuf的地址,决定了信号量的行为
这里struct sembuf的定义为:

struct sembuf {unsigned short sem_num;  	// 信号量在集合中的索引short sem_op;   			// 操作值short sem_flg;  			// 操作标志
};

sem_num:要操作的信号量的索引,当集合内有多个信号量时才有用
sem_op
 大于0表示释放资源,信号量值增加sem_op
 小于0表示获取资源,信号量值减去sem_op,若操作后为值为负则不进行操作并阻塞线程
 等于0表示阻塞等待信号量值为0
sem_flg
=0 表示阻塞等待,默认操作
=IPC_NOWAIT 时表示不阻塞,直接返回错误,对应上面sem_op可能阻塞的情况
=SEM_UNDO 这个不做讨论

信号量控制:

int semctl(int semid, int semnum, int cmd, ...);

semid:信号量集ID
semnum:信号量在集合中的索引
cmd:控制命令
...:c语言中的变参数,当cmd为某些命令时需要追加参数,此时需要定义一个联合体
这个函数的用法使用代码来展示:

union semun {int val;                // SETVAL用的值struct semid_ds *buf;   // IPC_STAT, IPC_SET用的缓冲区unsigned short *array;  // GETALL, SETALL用的数组
};	// 用于追加参数的一个结构体,必须由用户定义// 初始化信号量集合中的第一个信号量值为1
union semun arg;
arg.val = 1;
if (semctl(semid, 0, SETVAL, arg) == -1) {perror("semctl SETVAL failed");
}// 获取信号量值
int val = semctl(semid, 0, GETVAL);
printf("Current semaphore value: %d\n", val);// 删除信号量集
if (semctl(semid, 0, IPC_RMID) == -1) {perror("semctl IPC_RMID failed");
}

简单代码案例

这里只是为了让大家理解信号量的用法,下一节还会有更加合适的案例去帮大家了解信号量。我们使用fork()创建了两个进程,子进程每两秒释放一个信号量,父进程每0.5秒获取一个信号量。信号量被初始化为5。
这里补充一点,对于fork()函数,我们之前讲过它会将当前进程内部的资源克隆一份。(不了解的同学请移步:linux多线(进)程编程——(2)身外化身fork())但是由于信号量是属于操作系统的,因此它不会fork不会克隆信号量,但是它创建出的子进程会继承信号量的操作权限。
完整代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>int main() {int semid = semget((key_t)1, 1, 0666 | IPC_CREAT);if (semid == -1) {printf("sem create fail\n");return 0;}struct sembuf buf = {0};union sempun {int val;                // 用于SETVALstruct semid_ds *buf;   // 用于IPC_STAT/IPC_SETunsigned short *array;  // 用于GETALL/SETALL} arg;	// 这个联合体用于追加参数,由用户自己定义与创建arg.val = 5;if(-1 == semctl(semid, 0, SETVAL, arg)) {	// 将信号量值初始化为5printf("sem init fail!\n"); semctl(semid, 0, IPC_RMID);return 0;}printf("sem init val is %d!\n", arg.val);pid_t id = fork();if(id < 0) {printf("fork ret -1, error!\n");semctl(semid, 0, IPC_RMID);return 0;}if(id == 0) {   // 子进程,每隔2秒释放一个信号量buf.sem_num = 0;buf.sem_op = 1;buf.sem_flg = 0;while(1) {semop(semid, &buf, 1);int val = semctl(semid, 0, GETVAL);printf("son: I release a semaphore, and the val is %d\n", val);sleep(2);   // 阻塞2秒}}else {      // 父进程,每隔0.5秒获取一个信号量buf.sem_num = 0;buf.sem_op = -1;	// 值小于1,获取信号量buf.sem_flg = 0;    // 设置为阻塞模式while(1) {semop(semid, &buf, 1);int val = semctl(semid, 0, GETVAL);printf("father: I get a semaphore, and the val is %d\n", val);usleep(500*1000);   // 阻塞0.5s}}semctl(semid, 0, IPC_RMID);return 0;
}

输出结果与分析
上面程序的执行结果如下所示。可见由于信号量初始值为5,父进程在程序开始时快速获取资源。等到信号量资源耗尽(值为0)时,父进程被阻塞,等待子进程释放资源。有意思的是,当子进程释放资源后,显示信号量的值是0而不是1。原因是由于父进程在阻塞等待,因此当子进程释放资源后,还没进入下一条指令,马上会被父进程拿走。最后子进程看到的信号量的值为0。

lol@hyl:~/work/linux_study/sem$ ./p1
sem init val is 5!
father: I get a semaphore, and the val is 4
son: I release a semaphore, and the val is 5
father: I get a semaphore, and the val is 4
father: I get a semaphore, and the val is 3
father: I get a semaphore, and the val is 2
son: I release a semaphore, and the val is 3
father: I get a semaphore, and the val is 2
father: I get a semaphore, and the val is 1
father: I get a semaphore, and the val is 0
son: I release a semaphore, and the val is 0
father: I get a semaphore, and the val is 0
son: I release a semaphore, and the val is 0
father: I get a semaphore, and the val is 0
son: I release a semaphore, and the val is 0
father: I get a semaphore, and the val is 0
son: I release a semaphore, and the val is 0
father: I get a semaphore, and the val is 0

强调一点,这里对信号量的增加删除操作全部都是由操作系统内核与底层硬件支持的原子操作,不会出现普通变量那种被打断的情况,因此信号量是进程间安全的。下面这张图就表示了使用信号量的车库:
在这里插入图片描述

小结

这节课我们讲解了用于实现共享内存进程间同步的信号量。
我们要知道为什么不能使用普通变量来实现进程间计数,以及为什么可以使用信号量实现进程间计数。(原子性)
大家还要熟悉一下相关的信号量的函数。
关于信号量的具体使用,为了方便大家理解,我将在下一节将其与共享内存结合,并且提供几个案例,让大家更好的理解信号量的用法。
传送阵:linux多线(进)程编程——(9)信号量(二)

结束语

在信号量的帮助,共享内存又一次成为了修真界人们经常使用的传音术。

相关文章:

linux多线(进)程编程——(9)信号量(一)

前言 在找到了共享内存存在的问题后&#xff0c;进程君父子着手开始解决这些问题。他们发明了一个新的神通——信号量。 信号量 信号量是一个计数器&#xff0c;用于管理对共享资源的访问权限。主要特点包括&#xff1a; &#xff08;1&#xff09;是一个非负整数 &#xff…...

PFLM: Privacy-preserving federated learning with membership proof证明阅读

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目…...

关闭111端口监听

默认rpcbind服务会使用111端口&#xff0c;如果想禁用111端口&#xff0c;只需要禁用rpcbind服务即可&#xff1a; systemctl stop rpcbind.socket systemctl disable rpcbind.socket#检查111端口是否禁用成功 netstat -tuln |grep 111rpcbind 服务详解 rpcbind 服务&#xf…...

C++中的引用:深入理解与实用示例

文章目录 C中的引用&#xff1a;深入理解与实用示例一、引用的基本概念二、引用作为别名的应用三、引用作为函数参数四、指针与引用的区别五、常量引用六、引用与返回值七、总结 C中的引用&#xff1a;深入理解与实用示例 在C编程中&#xff0c;“引用”是一个强大而重要的概念…...

图片转base64 - 加菲工具 - 在线转换

图片转base64 - 加菲工具 先进入“加菲工具” 网 打开 https://www.orcc.top&#xff0c; 选择 “图片转base64”功能 选择需要转换的图片 复制 点击“复制”按钮&#xff0c;即可复制转换好的base64编码数据&#xff0c;可以直接用于img标签。...

opencv 对图片的操作

对图片的操作 1.图片镜像旋转&#xff08;cv2.flip()&#xff09;2 图像的矫正 1.图片镜像旋转&#xff08;cv2.flip()&#xff09; 图像的旋转是围绕一个特定点进行的&#xff0c;而图像的镜像旋转则是围绕坐标轴进行的。图像的镜像旋转分为水平翻转、垂直翻转、水平垂直翻转…...

LabVIEW数据采集与传感系统

开发了一个基于LabVIEW的智能数据采集系统&#xff0c;该系统主要通过单片机与LabVIEW软件协同工作&#xff0c;实现对多通道低频传感器信号的有效采集、处理与显示。系统的设计旨在提高数据采集的准确性和效率&#xff0c;适用于各种需要高精度和低成本解决方案的工业场合。 项…...

【Easylive】​​Gateway模块 bootstrap.yml 解析

【Easylive】项目常见问题解答&#xff08;自用&持续更新中…&#xff09; 汇总版 Gateway模块 bootstrap.yml 常规解析 该配置文件定义了 Spring Cloud Gateway 的核心配置&#xff0c;包括 环境配置、服务注册、动态路由规则 等。以下是逐项解析&#xff1a; 1. 基础配…...

matlab 环形单层柱状图

matlab 环形单层柱状图 matlab 环形单层柱状图 matlab 环形单层柱状图 图片 图片 【图片来源粉丝】 我给他的思路是&#xff1a;直接使用风玫瑰图可以画出。 rose_bar 本次我的更新和这个有些不同&#xff01;是环形柱状图&#xff0c;可调节细节多&#xff1b; 只需要函数…...

文献×汽车 | 基于 ANSYS 的多级抛物线板簧系统分析

板簧系统是用于减弱或吸收动态系统中发生的应力、应变、偏转和变形等破坏性因素的机械结构。板簧系统可能对外力产生不同的响应&#xff0c;具体取决于其几何结构和材料特性。板簧系统的计算机辅助分析对于高精度确定系统的变形特性和结构特性至关重要。 在这项工作中&#xff…...

MySQL:如何用关系型数据库征服NoSQL核心战场?

写在前面&#xff1a;当SQL遇见NoSQL的十年之变 2012年MongoDB掀起文档数据库革命时&#xff0c;开发者们不得不在灵活性与事务一致性之间做痛苦抉择。十年后的今天&#xff0c;MySQL 8.0的JSON功能已实现&#xff1a; ✅ 二进制存储效率超越传统BLOB 40% ✅ 多值索引使JSON查…...

分布式之CAP原则:理解分布式系统的核心设计哲学

声明&#xff1a;CAP中的P原则都是需要带着的 在分布式系统的设计与实践中&#xff0c;CAP原则&#xff08;又称CAP定理&#xff09;是开发者必须掌握的核心理论之一。它揭示了分布式系统在一致性&#xff08;Consistency&#xff09;、可用性&#xff08;Availability&#x…...

RHCE 练习二:通过 ssh 实现两台主机免密登录以及 nginx 服务通过多 IP 区分多网站

一、题目要求 1.配置ssh实现A&#xff0c;B主机互相免密登录 2.配置nginx服务&#xff0c;通过多ip区分多网站 二、实验 实验开始前需准备两台 linux 主机便于充当服务端以及客户端&#xff0c;两台主机 IP 如下图&#xff1a; 实验1&#xff1a;配置 ssh 实现 A&#xff0…...

瑞吉外卖-分页功能开发中的两个问题

1.分页功能-前端页面展示显示500 原因&#xff1a;项目启动失败 解决&#xff1a;发现是Category实体类中&#xff0c;多定义了一个删除字段&#xff0c;但是我数据库里面没有is_deleted字段&#xff0c;导致查询数据库失败&#xff0c;所以会导致500错误。因为类是从网上其他帖…...

工业物联网安全网关 —— 安全OTA升级签名验证

这里写目录标题 工业物联网安全网关 —— 安全OTA升级签名验证一、项目背景与简介1.1 背景介绍1.2 OTA升级的安全挑战1.3 项目目标二、理论基础与关键技术2.1 数字签名基础2.2 OTA升级签名验证原理2.3 关键技术与安全算法三、系统架构设计3.1 系统模块划分3.2 系统架构图(Merm…...

生信分析平台Galaxy是使用什么语言编程?是R语言吗?

Galaxy平台是一个基于**Python**开发的开放源代码生物信息学分析平台&#xff0c;而非主要依赖R语言。以下是关键细节&#xff1a; 1. **核心语言** - **后端**&#xff1a;主要用**Python**&#xff08;Django/Flask框架&#xff09;实现服务器逻辑、工具集成和API。 …...

【Rust 精进之路之第10篇-借用·规则】引用 (``, `mut`):安全、高效地访问数据

系列: Rust 精进之路:构建可靠、高效软件的底层逻辑 作者: 码觉客 发布日期: 2025年4月20日 引言:所有权的“限制”与“变通”之道 在上一篇【所有权核心】中,我们揭示了 Rust 如何通过所有权规则和移动 (Move) 语义来保证内存安全,避免了垃圾回收器的同时,也防止了诸…...

基于瑞芯微RK3576国产ARM八核2.2GHz A72 工业评估板——Docker容器部署方法说明

前 言 本文适用开发环境: Windows开发环境:Windows 7 64bit、Windows 10 64bit Linux开发环境:VMware16.2.5、Ubuntu22.04.5 64bit U-Boot:U-Boot-2017.09 Kernel:Linux-6.1.115 LinuxSDK:LinuxSDK-[版本号](基于rk3576_linux6.1_release_v1.1.0) Docker是一个开…...

Kafka安全认证技术:SASL/SCRAM-ACL方案详解

#作者 &#xff1a;张桐瑞 文章目录 1Kafka安全认证技术介绍2基础设置3 配置SASL/SCRAM认证3.1编写server.properties配置3.2编写kafka.conf密码文件3.3编写user.properties配置文件3.4编写kafka-run-class.sh脚本文件3.5Zk中增加kafka用户3.6启动kafka进程 1Kafka安全认证技术…...

MySQL VS SQL Server:优缺点全解析

数据库选型、企业协作、技术生态、云数据库 1.1 MySQL优缺点分析 优点 开源免费 社区版完全免费&#xff0c;适合预算有限的企业 允许修改源码定制功能&#xff08;需遵守GPL协议&#xff09; 跨平台兼容性 支持Windows/Linux/macOS&#xff0c;适配混合环境部署 云服务商…...

探索 Flowable 后端表达式:简化流程自动化

什么是后端表达式&#xff1f; 在 Flowable 中&#xff0c;后端表达式是一种强大的工具&#xff0c;用于在流程、案例或决策表执行期间动态获取或设置变量。它还能实现自定义逻辑&#xff0c;或将复杂逻辑委托…… 后端表达式在 Flowable 的后端运行&#xff0c;无法访问前端…...

Mysql的redolog

保证事务持久性&#xff0c;用于崩溃恢复&#xff0c;崩溃恢复时&#xff0c;把redo上记载的页读到内存&#xff0c;对其修改&#xff0c;变为脏页&#xff0c;刷盘运用于WAL技术&#xff0c;将随机写改为顺序写 redo log有三种状态&#xff1a; 存在 redo log buffer 中&…...

HDFS入门】HDFS安全与权限管理解析:从认证到加密的完整指南

目录 引言 1 认证与授权机制 1.1 Kerberos认证集成 1.2 HDFS ACL细粒度控制 2 数据加密保护 2.1 传输层加密(SSL/TLS) 2.2 静态数据加密 3 审计与监控体系 3.1 操作审计流程 3.2 安全监控指标 4 权限模型详解 4.1 用户/组权限模型 4.2 umask配置原理 5 安全最佳实…...

React-useImperativeHandle (forwardRef)

我们会遇到这样的场景&#xff1a;某个组件想要暴露一些方法&#xff0c;来供外部组件来调用。例如我们在开发form表单的时候&#xff0c;就需要把设置表单值、重置值、提交等方法暴露给外部使用。会有如下代码&#xff1a; import { forwardRef } from react;const Form for…...

Mediatek Android13 设置Launcher

概述: 本章将围绕Launcher讲述两种修改默认Launcher的情况。 一:完全覆盖 第一种方法和预置apk类似,区别在于增加LOCAL_OVERRIDES_PACKAGES说明,该方法会完全覆盖系统默认的Launcher。 关于如何预置apk,可见另一篇文章: Mediatek Android13 预置APP-CSDN博客 修改A…...

性能比拼: Go vs Java

本内容是对知名性能评测博主 Anton Putra Go (Golang) vs Java: Performance Benchmark 内容的翻译与整理, 有适当删减, 相关指标和结论以原作为准 在本视频中&#xff0c;我们将比较 Go 和 Java。 我们将基于 Golang 的 Fiber 框架和 Java 的 Spring Boot 创建几个简单的应用…...

ElMessageBox消息弹框(vue3总结)

一 展示各种内容 const checkCheckbox (check: any, formEl: any) > {ElMessageBox({title: "服务协议及隐私权政策",message: h("p", null, [h("span", null, "我已阅读并同意 "),h("span",{style: "color: #477F…...

Jupyter Notebook 中切换/使用 conda 虚拟环境的方式(解决jupyter notebook 环境默认在base下面的问题)

使用 nb_conda_kernels 添加所有环境 一键添加所有 conda 环境 conda activate my-conda-env # this is the environment for your project and code conda install ipykernel conda deactivateconda activate base # could be also some other environment conda in…...

CLIP | 训练过程中图像特征和文本特征的在嵌入空间中的对齐(两个投影矩阵的学习)

在多模态学习&#xff08;Multimodal Learning&#xff09;中&#xff0c;投影矩阵 W i W_i Wi​ 和 W t W_t Wt​ 是通过训练过程学习得到的。它们的作用是将图像特征 I f I_f If​ 和文本特征 T f T_f Tf​ 映射到一个共享的嵌入空间&#xff08;embedding space&#xf…...

Java面试实战:从Spring Boot到微服务的深入探讨

Java面试实战&#xff1a;从Spring Boot到微服务的深入探讨 场景&#xff1a;电商场景的面试之旅 在某互联网大厂的面试间&#xff0c;面试官李老师正襟危坐&#xff0c;而对面坐着的是传说中的“水货程序员”赵大宝。 第一轮&#xff1a;核心Java与构建工具 面试官&#x…...