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

进程间通信 之 信号量

目录

前言

一、信号量引例

1.不加控制模拟使用打印机

二、信号量介绍

1.什么是信号量?

2.为什么要用信号量?

三、信号量的核心操作:PV原语

1.P操作(等待操作 / wait / down)

2.V操作(释放操作 / signal / up)、

四、信号量的接口介绍

五、有控制的使用打印机(信号量的使用)


前言

信号量是进程间通信中一种非常重要的同步机制,但它与管道、消息队列不同——信号量本身并不传递数据,而是用于解决进程间同步与互斥的问题,保护共享资源的有序访问


一、信号量引例

1.不加控制模拟使用打印机

比如:进程 a 和进程 b 模拟访问打印机,进程 a 输出第一个字符‘ a’表示开始使用打印机,输出第二个字符‘ a’表示结束使用, b 进程操作与 a 进程相同。


代码示例:

a.c:

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <assert.h> int main() { int i=0; for(;i<5;i++) { printf("A"); fflush(stdout); int n=rand()%3; sleep(n); printf("A"); fflush(stdout); n=rand()%3; sleep(n); } }

b.c:

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <assert.h> int main() { int i=0; for(;i<5;i++) { printf("B"); fflush(stdout); int n=rand()%3; sleep(n); printf("B"); fflush(stdout); n=rand()%3; sleep(n); } }

上述例子的正确结果应该是:AABB成对出现
由于打印机同一时刻只能被一个进程使用,所以输出结果不应该出现 ABAB这样交替的结果
所以上面得到的都是错误结果.


二、信号量介绍

1.什么是信号量?

信号量是用于控制多个进程对共享资源的访问,保证同一时刻只能由一个进程对某个资源进程访问。

  • 当信号量值 > 0 时,表示当前可用的资源数量
  • 当信号量值 = 0 时,表示资源已被用完
  • 当信号量值 < 0 时,其绝对值表示正在等待该资源的进程数量

2.为什么要用信号量?

考虑多个进程同时访问共享资源(如打印机、共享内存)的情况:

// 没有同步的多进程输出 // 两个进程同时向屏幕输出,结果会混乱 父进程输出: "P Start." 子进程输出: "C Start." 父进程输出: "P End." 子进程输出: "C End." // 输出交错混杂,无法区分[citation:1]

这种资源竞争问题需要通过信号量来解决,确保进程对临界资源的互斥访问和同步协调

三、信号量的核心操作:PV原语

信号量是一个特殊的变量,一般取正数值。

它的值代表允许访问的资源数目,获取资源时,需要对信号量的值进行原子减一,该操作被称为P 操作

当信号量值为 0时,代表没有资源可用, P 操作会阻塞。

释放资源时,需要对信号量的值进行原子加一,该操作被称为V操作

信号量主要用来同步进程。

信号量的值如果只取 0,1, 将其称为二值信号量。如果信号量的值大于 1,则称之为计数信号量

信号量是一个特殊的变量,对信号量的操作都是一个原子操作


临界资源:同一时刻,只允许被一个进程或者线程访问的资源;(比如打印机)

临界区:访问临界资源的代码段;


1.P操作(等待操作 / wait / down)

// P操作伪代码 void P(semaphore S) { S = S - 1; // 申请一个资源 if (S < 0) { // 没有可用资源,进程阻塞并加入等待队列 block_process(); } }

P操作的含义:进程请求一个资源单位。

  • 如果 S ≥ 0:进程获得资源,继续执行
  • 如果 S < 0:进程被阻塞,进入等待队列。

2.V操作(释放操作 / signal / up)、

// V操作伪代码 void V(semaphore S) { S = S + 1; // 释放一个资源 if (S <= 0) { // 有进程在等待,唤醒一个 wakeup_process(); } }

V操作的含义:进程释放一个资源单位。

  • 如果 S > 0:没有进程在等待
  • 如果 S ≤ 0:唤醒等待队列中的一个进程

四、信号量的接口介绍

(1)semget int semget(key_t key,int nsems,int semflg); 创建或者获取一个已经存在的信号量; key:两个进程使用相同的key值,就可以使用同一个信号量; nsems:创建几个信号量; semflg:标志位;如果为创建:IPC_CREAT; 如果为全新创建,也就是不知道是否有人创建过,则IPC_CREAT|IPC_EXCL, 就是如果没有则创建,如果有则创建失败 ; (2)semop: int semop(int semid,struct sembuf *sops,unsigned nsops); 对信号量进行改变,做P操作或者V操作; semid:信号量的id号,也就是刚才semget的返回值;说明对哪个信号量进行操作; sops:结构体指针,指向sembuf的结构体指针, sembuf结构体有三个成员变量: sem_num表示信号量的编号(即指定信号量集中的信号量下标); sem_op表示是p还是v操作;1为v操作(加1),-1为p操作(减1); sem_flg为标志位; (3)semctl: int semctl(int semid,int semnum,int cmd,...); 对信号量进行控制:初始化/删除信号量 semid:信号量id; semnum:信号量编号; cmd:命令:SETVAL:初始化信号量; IPC_RMID:删除信号量; **注意**:联合体semun,这个联合体需要自己定义;

五、有控制的使用打印机(信号量的使用)

代码示例:

//sem.h

#include <sys/sem.h> #include <unistd.h> #include <stdio.h> union semun{ int val; }; void sem_init(); void sem_p(); void sem_v(); void sem_destroy();

//sem.c

#include "sem.h" static int semid=-1; void sem_init() { semid=semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600); if(semid==-1) { semid=semget((key_t)1234,1,0600); if(semid==-1) { perror("semget error!\n"); } } else { union semun a; a.val=1; if(semctl(semid,0,SETVAL,a)==-1) { perror("semctl init error!"); } } } void sem_p() { struct sembuf buf; buf.sem_num=0; buf.sem_op=-1; buf.sem_flg=SEM_UNDO; if(semop(semid,&buf,1)==-1) { perror("p error!\n"); } } void sem_v() { struct sembuf buf; buf.sem_num=0; buf.sem_op=1; buf.sem_flg=SEM_UNDO; if(semop(semid,&buf,1)==-1) { perror("v error!\n"); } } void sem_destroy() { if(semctl(semid,0,IPC_RMID)==-1) { perror("destroy sem error!\n"); } }

//a.c

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <assert.h> #include "sem.h" int main() { int i=0; sem_init(); for(;i<5;i++) { sem_p(); printf("A"); fflush(stdout); int n=rand()%3; sleep(n); printf("A"); fflush(stdout); sem_v(); n=rand()%3; sleep(n); } sleep(10); sem_destroy(); }

//b.c

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <assert.h> #include "sem.h" int main() { int i=0; sem_init(); for(;i<5;i++) { sem_p(); printf("B"); fflush(stdout); int n=rand()%3; sleep(n); printf("B"); fflush(stdout); sem_v(); n=rand()%3; sleep(n); } }

运行结果保证了A和B都是成对出现的。

http://www.jsqmd.com/news/501742/

相关文章:

  • 刷题笔记:力扣第53题-最大子数组和
  • 11-Kotlin高阶特性-协程
  • 天虹购物卡回收小技巧 - 团团收购物卡回收
  • 2026海南GEO优化服务商推荐排名发布:念奴娇稳居榜首,七大服务商精准赋能自贸港企业 - 江湖评测
  • 2026年3月郑州黄金回收店推荐排行榜单:五大机构对比评测与选择指南 - 品牌推荐
  • 收藏!大模型 Agent 项目面试实战:如何讲好 PaiFlow 故事?亮点与难点全解析
  • 激光雷达“线”越多,自动驾驶能力就越强?一场关于感知“像素”的深度辨析
  • 计算机毕业设计springboot在线音乐服务系统 Spring Boot框架下的云端音乐流媒体播放平台开发 基于Java Web技术的智能音乐分享与社区互动系统构建
  • 计算机毕业设计springboot中华汉字学习平台 基于SpringBoot的汉字文化教育传播平台 SpringBoot架构下的传统汉字数字化学习系统
  • 《服务器硬件基础(九)——BIOS/UEFI详解:鲲鹏920关键配置项与测试注意事项》
  • Flutter Beta 版本引入 ScrollCacheExtent ,并修复长久存在的 shrinkWrap NaN 问题
  • 前端-小米商城静态版复刻总结
  • HCIP-AI-EI Developer V2.5 第五、六章笔记
  • GEE案例分析:基于Dynamic World 数据的农用地识别活跃与休耕农田
  • java之抽象类和接口
  • 万爱通礼品卡怎么回收最划算?线上流程分享 - 团团收购物卡回收
  • Python基于卷积神经网络的学情分析系统【附源码、文档说明】
  • 一键生成以假乱真的扫描件!LookScanned:cpolar 内网穿透实验室第 786成功挑战
  • 2026年3月郑州黄金回收店推荐排行榜单:五大机构客观对比与深度评测分析 - 品牌推荐
  • 洛谷:P1554 [USACO06DEC] 梦中的统计 Dream Counting B
  • 博世 HBA 液压制动辅助系统性能规范详解
  • 把杂乱网址装进口袋!Dashlet 轻量仪表盘 : cpolar 内网穿透实验室第 757 个成功挑战
  • 不学 Python,Java 也能调大模型?15 分钟跑通第一个 AI 接口(Java 架构师的 AI 工程笔记 01)
  • Java架构设计:密码加密设计最佳实践(从入门到工业级落地)
  • 什么是原型链(Prototype Chain)?proto和prototype的关系与区别是什么?
  • 【零基础入门】Python机器视觉第五阶段:目标检测实战(YOLOv8)
  • Q312B三菱主基板
  • SpringBoot 配置文件核心用法(Properties YAML)
  • Python 全栈实战 · 第8章
  • 《QGIS快速入门与应用基础》226:添加地图框工具(布局工具栏)