进程与线程:09 进程同步与信号量

课程引入:进程同步与信号量

接下来这节课开始,我们再开始讲多进程图像。讲多进程图像的下一个点,前面我们讲清楚了多进程图像要想实现切换,调度是如何做的。同时,多个进程放在内存中,就会存在多进程合作的情况,而这种合作应该是合理有序的,这部分内容就是进程同步——让多进程之间的合作变得合理有序。那么怎么来实现这种合理有序呢?就要靠信号量。这堂课我们要讲清楚为什么会有信号量,以及如何依靠信号量来实现多个进程推进的合理有序,即同步。

现实案例:进程合作与同步的重要性

首先从一个例子看起,在现实社会中,司机和售货员就如同两个进程,司机的动作是启动车、运行、到站停车,售货员的动作是关门、售票、开门 ,他们各自有一套执行流程。这两个进程在同一台车上,为完成车辆的合理有序行驶,必须进行合作。在这里插入图片描述

  • 不合作的后果:如果二者执行顺序没有约束,比如售票员开门售票时,司机启动车辆,就会造成严重后果。所以,司机启动车辆不能随意进行,需要等待一个信号,比如售票员卖完票告知可以走了;同样,售票员开门也不是随便进行的,要等车辆到站停车的信号。
  • 合作与同步的体现:司机等待售票员关门的信号再启动车辆,售票员在车辆到站后得到停车信号才开门,这就体现了进程间的合作。一个进程等待信号,另一个进程在合适的时候发送信号,从而使多个进程按照一定顺序向前推进,这就是同步。每个进程有自己的执行程序,但不是每条程序都能随便执行,有时需要停下来等待信号,当收到信号后再继续执行,这就是多进程合理有序的合作与同步。

技术案例:生产者 - 消费者模型中的同步问题

接下来以生产者 - 消费者模型为例进一步说明。有一个共享缓冲区,生产者不断向里放内容,每放完一个 counter 加一;消费者不断从里取内容,每取出一个 counter 减一 ,这是典型的多进程合作场景。在这里插入图片描述

  • 同步需求:在这个模型中,也需要合理有序的推进。当生产者发现 counter 等于缓冲区大小 buff size ,即缓冲区满了,就不能继续放入,必须等待;同理,当缓冲区为空时,消费者也应该停止。在这里插入图片描述
    所以,进程同步的关键在于分析进程在哪些地方该停、什么时候该走。在生产者 - 消费者模型中,缓冲区满时生产者停,消费者消费后产生空闲缓冲区,就给生产者发信号让其继续;缓冲区空时消费者停,生产者生产后给消费者发信号。在这里插入图片描述

  • 信号的局限性:仅依靠信号存在问题。例如,当缓冲区满时,生产者 p1 尝试放入会因 counter 等于 buff sizesleep ,之后另一个生产者 p2 进来,同样会 sleep 。此时若消费者执行一次循环,取出一个内容, counter 变为 buff size - 1 ,消费者判断缓冲区未满,认为无人等待缓冲区,不会再发信号唤醒 p2 ,导致 p2 永远无法唤醒。这说明单纯依靠 counter 进行语义判断不足,不仅需要知道缓冲区中空闲个数,还需知道有多少进程在睡眠等待。

信号量的引入与原理在这里插入图片描述

为解决上述问题,引入信号量。信号量是一个整数,它能记录更多信息。例如信号量等于 -2 ,表示有两个进程在等待 。当消费者执行时,发现信号量为 -2 ,就会唤醒阻塞队列头部的进程(如 p1 ),同时信号量变为 -1 ;再次执行,唤醒 p2 ,信号量变为 0
在这里插入图片描述

  • 信号量的语义:信号量为负数时,表示有进程阻塞,其绝对值代表等待进程的数量;为 0 时,表示没有进程等待,但也没有可用资源;为正数时,表示有可用资源,数值代表资源数量 。如消费者执行使信号量变为 1 ,表示还有一个空闲缓冲区,此时若有新的生产者 p3 来,无需睡眠可直接执行,执行后信号量变为 0 ;再有生产者来,信号量变为 -1 ,该生产者需阻塞等待。
  • 基于信号量的进程决策:进程根据信号量的值决定是否等待或唤醒其他进程。生产者申请使用资源(如空闲缓冲区)时,若信号量为负或零,说明资源不足或已用完,需等待;消费者释放资源(产生空闲缓冲区)时,若信号量为负,说明有进程在等待,需唤醒一个等待进程。

信号量与资源等待的关系

我们可以根据这个习题来回答,对于一种数量为八的资源,思考进程等待的原因。进程等待肯定是因为申请资源时没有可用资源了。在进程同步中,竞争合作体现为走走停停,而“停滞”是其中的核心,所以明确进程何时等待至关重要。当一个进程访问资源却发现没有资源时,就会进入等待状态。在这里插入图片描述

这种资源对应的信号量初值应该设为八,这表示初始状态下可以使用八个该资源。当信号量的值变为零时,意味着没有资源剩余,若值再变为负数,进程就需要等待。当信号量的值为二时,说明还有两个资源可供使用,此时没有进程在等待该资源;而当信号量的值为 -2 时,则表示有两个进程正在等待该资源 。

通过信号量的值,我们能够判断系统中有多少进程在等待资源,以及还有多少资源可供使用。基于这样的判断,我们可以控制进程的执行与暂停,从而实现进程同步,其核心就在于依据信号量的值来判断进程何时该“走”、何时该“停”。

信号量的核心概念与操作

  1. 信号量的定义与作用
    进程之间的同步是多个进程走走停停的合理有序推进,判断何时停要看信号量的值。当信号量为负值或 0 时,进程申请信号量会变成负值,此时进程等待;其他进程根据信号量的值,若为负,在释放信号量时进行唤醒操作;若为正,直接累加,无需发信号。

  2. 信号量的实现方式
    在编程实现中,判断进程是否需要等待资源是通过调用函数来完成的,这就涉及到信号量的具体实现。信号量的核心是一个整数,它记录着资源的相关信息。为了方便用户操作,我们通过定义 PV 操作这两个接口函数来实现对信号量的控制。在这里插入图片描述

    • P 操作:当进程想要申请资源,判断自己是否应该暂停时,就调用 P 操作。以 P(sem) 为例,执行该操作时,首先将信号量 sem 的值减 1。这是因为进程申请资源,相当于资源数量减少。如果减 1 后信号量的值小于 0,说明在本次申请之前,资源要么已经没有剩余(值为 0),要么已经处于供不应求的状态(值为负),当前进程无法获得资源,此时进程就会进入睡眠状态,并被放入与该信号量相关联的阻塞队列中。例如,生产者进程每次使用空闲缓冲区时,就需要对空闲缓冲区对应的信号量执行 P 操作,以此判断是否有空闲缓冲区可供使用,如果没有则进入等待。
    • V 操作:有进程等待,就需要有唤醒操作,这就是 V 操作的作用。当进程释放资源时,会调用 V 操作。执行 V 操作时,将信号量的值加 1 ,这表示资源数量增加。如果加 1 后信号量的值仍然小于等于 0,说明在释放资源之前,有进程在等待该资源(信号量为负表示等待进程数,为 0 表示刚有进程等到资源),此时就需要调用 wake up 函数,从阻塞队列中唤醒一个进程;如果加 1 后信号量的值大于 0 ,则表示没有进程在睡眠等待,无需进行唤醒操作。比如消费者进程产生空闲缓冲区后,就会对相应的信号量执行 V 操作。
    • 系统调用:由于信号量操作涉及到进程睡眠等在内核态完成的操作,所以 PV 操作需要做成系统调用,这样上层应用程序就能通过调用系统调用来使用信号量。其中,P 操作源于荷兰语“test”,表示测试是否需要阻塞;V 操作源于荷兰语“increment”,表示增加资源数量,进而实现唤醒等待进程的功能 。

信号量解决生产者 - 消费者问题

利用信号量及其 PV 操作,可以有效解决生产者 - 消费者问题,实现进程间的同步与合作。在解决该问题时,关键在于分析生产者和消费者何时会暂停,并据此定义相应的信号量。在这里插入图片描述

  1. 分析生产者与消费者的等待条件
    • 生产者:当缓冲区满时会停,所以定义一个信号量 empty 表示空闲缓冲区个数,初值为 buff_size 。生产者每次操作前先执行 P(empty) ,测试 empty 是否为 0 ,即缓冲区是否满,若满则等待。当消费者释放空闲缓冲区时,执行 V(empty) 增加 empty 的值 。
    • 消费者:当缓冲区没有内容时会停,定义一个信号量 full 表示已生产内容的个数,初值为 0 。消费者每次操作前先执行 P(full) ,测试 full 是否为 0 ,即是否有内容,若无则等待。当生产者生产内容后,执行 V(full) 增加 full 的值 。
  2. 互斥信号量实现共享资源互斥访问
    共享缓冲区(可视为文件)的操作需要互斥,即同一时刻只能有一个进程访问。定义一个互斥信号量 mutex ,初值为 1 。生产者和消费者在访问共享缓冲区前,先执行 P(mutex) ,若 mutex 等于 1 ,则变为 0 ,进程进入;访问结束后执行 V(mutex) 释放资源,使其他进程可以进入 。

通过上述信号量的设置以及 PV 操作的合理运用,依据信号量数值所代表的语义,准确判断进程是否需要睡眠或唤醒其他进程,从而实现了生产者和消费者之间执行过程的合理有序,最终解决了进程同步问题,实现了二者的合作 。

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

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

相关文章

【愚公系列】《Manus极简入门》036-物联网系统架构师:“万物互联师”

🌟【技术大咖愚公搬代码:全栈专家的成长之路,你关注的宝藏博主在这里!】🌟 📣开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主! &#x1f…

MySQL 8.0 OCP 英文题库解析(四)

Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题26~30 试题26:…

什么是原码和补码

补码的本质确实是模运算(Modular Arithmetic),这是理解补码为何能统一加减法的核心数学原理。下面用最通俗的语言和例子解释清楚: —### 1. 先理解什么是“模运算”- 模运算就是“周期性计数”,比如钟表: -…

笔记项目 day02

一、用户登录接口 请求参数: 用loginDTO来封装请求参数,要加上RequestBody注解 响应参数: 由于data里内容较多,考虑将其封装到一个LoginUser的实体中,用户登陆后,需要生成jwtToken并返回给前端。 登录功…

2025年土木建筑与水利工程国际会议(ICCHE 2025)

2025 International Conference on Civil and Hydraulic Engineering (ICCHE 2025) (一)会议信息 会议简称:ICCHE 2025 大会地点:中国银川 投稿邮箱:icchesub-paper.com 收录检索:提交Ei Compendex,CPCI,C…

运行Spark程序-在shell中运行1

(一)分布式计算要处理的问题 【老师提问:分布式计算要面临什么问题?】 【老师总结】 分布式计算需要做到: 1.分区控制。把大的数据拆成一小份一小份的(分区,分片)让多台设备同时计算…

一文理清人工智能,机器学习,深度学习的概念

目录 一、人工智能的起源与核心范畴(1950-1980) 1.1 智能机器的最初构想 1.2 核心范畴的初步分化 二、机器学习的兴起与技术分化(1980-2010) 2.1 统计学习的黄金时代 2.2 神经网络的复兴与子集定位 2.3 技术生态的形成与AI…

《Effective Python》第1章 Pythonic 思维总结——编写优雅、高效的 Python 代码

《Effective Python》第1章 Pythonic 思维总结——编写优雅、高效的 Python 代码 在编程的世界里,每个语言都有其独特的风格和最佳实践。对于 Python 而言,“Pythonic”已经成为描述遵循 Python 特定风格的代码的代名词。这种风格不仅让代码更易读、更简…

MySQL 事务(二)

文章目录 事务隔离性理论理解隔离性隔离级别 事务隔离级别的设置和查看事务隔离级别读未提交读提交(不可重复读) 事务隔离性理论 理解隔离性 MySQL服务可能会同时被多个客户端进程(线程)访问,访问的方式以事务方式进行一个事务可能由多条SQL…

代码仓提交分支规范

以下是我部门开发时用的分支规范,参考于Linux社区 Tips 分支命名通常遵循一些最佳实践和规则,以便使分支的用途和内容清晰易懂,就在写一个文档的主题一样。 功能分支 (Feature Branches) 用于开发新功能。 命名格式:feature/功能名…

Google Earth Engine(GEE) 代码详解:批量计算_年 NDVI 并导出(附 Landsat 8 数据处理全流程)

一、代码整体目标 基于 Landsat 8 卫星数据,批量计算 2013-2020 年研究区的 NDVI(归一化植被指数),实现去云处理、数据合成、可视化及批量导出为 GeoTIFF 格式,适用于植被动态监测、生态环境评估等场景。 二、代码分步解析(含核心原理与易错点) 1. 加载并显示研究区边…

Maven 处理依赖冲突

Maven处理依赖冲突 什么是依赖冲突?如何解决?Maven自动处理依赖冲突的规则路径优先原则第一声明优先原则注意 子模块覆盖父模块父模块声明dependency子模块覆盖dependency父模块声明dependencyManagement 子模块覆盖dependency父模块声明dependencyManag…

docker 安装 sqlserver2022 和注意点

一、前言 1、可以直接参考微软官方文档 快速入门:使用 Docker 运行 SQL Server Linux 容器映像,这里主要是说一些注意点和坑 二、安装 1、拉取镜像 docker pull mcr.microsoft.com/mssql/server:2022-latest2、创建挂载目录,这里只是比官方…

Dagster Pipes系列-1:调用外部Python脚本

本文是"Dagster Pipes教程"的第一部分,介绍如何通过Dagster资产调用外部Python脚本并集成到数据管道中。首先,创建Dagster资产subprocess_asset,利用PipesSubprocessClient资源执行外部脚本external_code.py,实现跨进程…

【SQL系列】多表关联更新

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

C++进阶学习:STL常用容器--map/multimap容器

1. map 容器基本概念 map 中所有元素都是 pair pair 中第一个元素为 key (键值) 起到索引运用 第二个元素为 value(实值) 所有元素都会根据元素的键值自动排序 本质: map/multimap 属于关联式容器 底层结构是用二…

let,const,var关键字的区别

let,const,var关键字 let,const,var都存在变量提升 它们都存在变量提升但是稍微有点不同 var变量声明会被提升到作用域的顶部,并且会被初始化为 undefinedlet 和 const:变量声明也会被提升到作用域的顶部,但不会被初…

Nuitka 已经不再安全? Nuitka/Cython 打包应用逆向工具 -- pymodhook

pymodhook是一个记录任意对Python模块的调用的库,用于Python逆向分析。 pymodhook库类似于Android的xposed框架,但不仅能记录函数的调用参数和返回值,还能记录模块的类的任意方法调用,以及任意派生对象的访问,基于pyob…

path环境变量满了如何处理,分割 PATH 到 Path1 和 Path2

要正确设置 Path1 的值,你需要将现有的 PATH 环境变量 中的部分路径复制到 Path1 和 Path2 中。以下是详细步骤: 步骤 1:获取当前 PATH 的值 打开环境变量窗口: 按 Win R,输入 sysdm.cpl,点击 确定。在 系…

SEMI E40-0200 STANDARD FOR PROCESSING MANAGEMENT(加工管理标准)-(一)

1 目的 物料(例如晶圆)加工在设备中的自动化管理与控制是实现工厂自动化的关键要素。本标准针对半导体制造环境中与设备内部物料处理相关的通信需求进行了规范。本标准规定了在加工单元接收到的指定材料所应适用的加工方法(例如Etch腔室需要Run哪支Recipe)。它阐述了物料加工的…