【408 操作系统】第二章 进程与线程 更新完毕
第二章 进程与线程
2.1.1 进程的概念、组成与特征
本节总览
- PCB + 程序段(指令序列) + 数据段(数据、变量)
- 特征:动态、并发、独立(独立获得资源、独立接受调度) 异步、结构
- 进程是系统进行资源分配和调度的一个独立单位 (学了线程后,线程是 真正的调度单位,但资源分配的单位仍是进程)
- PCB 记录进程信息:status, envs context,cpu info,res
- 特征:五大特性,重要的有动态性,独立性,异步性
程序是静态的,进程是动态的
给进程拍快照,得到的就是进程映像(某一时刻的进程信息)
PID —— 进程的身份证
PCB —— 你的所有信息都被刻在了PCB上!!!
PCB —— 有关于进程的详细信息
有印象即可
进程的组成 —— PCB + 程序段 + 数据段
PCB 给操作系统用
程序段、数据段 给进程用
程序的运行 与 进程的创建
程序或数据属于某个进程?
严格来说 ,进程是动态的;进程实体(映像)是静态的,相当于快照
再次反思,PCB 是给系统用的,程序段和数据段是给进程本身用的
PCB 是进程存在的唯一标志
2.1.2 进程的状态与转换、进程的组织
- 进程有几种状态? 5 = 3+ 2
- 就绪态和阻塞态的区别:就绪态万事俱备,只欠cpu;阻塞态,缺其他资源或事件
- 进程状态间的转换:
- 就绪态 -> 运行态。 进程被调度,cpu 竭诚为您服务!
- 运行态 -> 就绪态。服务结束,请您先暂作休息。
- 运行态 -> 阻塞态。 没有系统资源,或主动等待一个事件发生。
- 阻塞态 -> 就绪态。 就等你pua了。不对,就等你cpu了
New and ready
- 进程正在被创建,那就是创建态 New
- 进程被创建完成,其他资源也分配给它了,但CPU资源还不能给它,便进入“就绪态”Ready
Are you ready? Come on~~
运行态 Running
cpu 正在为您服务
阻塞态 Block
- 等待系统其他资源的分配,或等待其他进程的效应 (有CPU资源,但却一些其他的系统资源)
我等待的外卖还没来~ 还不能上车。
终止态 terminated
exit 886
一鲸落万物生。不带走一片尘土。
进程状态的转换关系图
还没进入状态的 UNUSED
- 正在创建的 EMBRYO
- 整装待发的 RUNNABLE
- 正在运行的 RUNNING
- 晚高峰堵车的 SLEEPING
- 即将逝去的 ZOMBIE
5 = 3 + 2
- 为什么分成3 + 2
- state 记录进程状态
进程的组织 —— 链接方式
像不像线性表??像不像队列?
进程的组织——索引方式
有点像hash 表
2.1.3 进程控制
2.1_3_进程控制.pdf
本节总览
- 进程控制,控制的是什么? 控制的是进程的转态转换。
- 为什么原语那么重要?原语的应用场景?原语的实现原理 ?
- 原语,一气呵成,不可被中断,所以其具备可靠性。
- 原语的重要应用场景就包括本节要学的,进程的状态转换。
- 原语的实现原理—— 关中断与开中断之间的程序执行 不可被打扰
提问:为什么用原语?原语的实现原理是什么?
答:原语执行后将一气呵成,不可中断;原语利用了开、关中断
提问:为什么需要用到原语?如果不用原语会出现哪些翻车的情况?
答:原语一气呵成;如果不用原语,有可能会存在 进程状态字被修改,但所处队列还没改过来的情况。
提问:如何实现原语?
答:“关中断”到“开中断”之间的所有指令都不会被中断所干扰,这些指令会被cpu一气呵成地执行。期间接受的中断,会在开中断指令执行后才被处理。
cpu 执行关中断指令后,就不会再检查/例会中断信号。直到执行开中断指令后才会恢复中断检查。
创建原语
提问:什么情况下会创建进程?
答:
用户登陆:分时系统,用户登陆成功。(java 中学过多线程,用户登陆会创建一个新的线程,可以类比一下)
作业调度:多道批处理系统,为进入内存的新作业创建新进程
提供服务:操作系统创建新的进程为用户提供服务
应用请求:创建子进程
撤消原语
提问:什么情况下会终止进程?
答:
正常结束:exit
异常结束:exception 整数除以0等异常
外界干预:用户主动杀死进程 ctrl + alt + del
阻塞与唤醒原语
为什么阻塞原语和唤醒原语必须成对使用啊?
阻塞原语干了啥?
找到pcb
保护进程的现场,将state 设置成 阻塞态
将pcb 插入相应事件的等待队列
引起阻塞的原因
等待系统分配资源
配合其他进程
等待事件
进程的唤醒要干啥?
在阻塞队列中找到pcb
state 改为就绪态
将pcb 挪到就绪队列,等待被调度
何时被唤醒?
等待的事件发生了!
切换原语
注:切换原语,是在运行态和就绪态之间的切换。前面的阻塞与唤醒,是在阻塞态和就绪态之间转换。
切换原语要保存运行环境的上下文。例如,切换前一时刻的PSW,PC 等寄存器 的值。
All in All
2.1.4 进程通信 IPC
2.1_4_进程通信..pdf
本节总览
什么是进程间通信?
进程间的数据交换。如微博的吃瓜文转发到微信。
为什么进程通信 需要操作系统支持?
因为进程的资源是操作系统分配的,且为了保证进程的安全,所以需要在操作系统的支持下才能完成进程间通信。
进程间通信的3种方式之共享存储
共享存储
共享存储:在内存种划分一块共享的空间,各线程互斥地访问,能实现进程间通信的效果。
共享存储有低级与高级之分
低级——基于数据结构的共享
高级——基于存储区的共享
前者慢;后者快
消息传递
消息传递还分为直接通信和信箱通信
直接通信方式
进程p 发信息
将信息挂载到进程Q 的消息队列
其实这个非常像ros 里面的进程通信
还有老韩的java基础,qq聊天项目中的多线程通信也是类似的实现,即规定一个消息格式/接口,每个线程的消息都必须遵循该格式
进程Q 取走信息
信箱/间接通信
进程p 发信息
信箱接收信息
进程Q 从信箱中取信息
注意:信箱在内核中
管道通信
pipe
I have an apple and I have a pie.I have an apple pie.
总结
三种通信方式——共享存储(基于数据结构+基于存储区)+ 消息通信(直接+信箱)+ 管道通信
都需要操作系统的支持
2.1.5 线程的概念
其实关键之处,还是并发的颗粒度问题。没有引入线程,并发的颗粒就是进程;引入线程后,颗粒是更细的线程。
提问:什么是线程? 为什么要引入线程?进程vs 线程?可不可以类比进程?
最初没有进程。程序只能依次串行执行。qq,网易云,只能串行执行,上qq就不能网易云,呜呜~
引入进程后。qq,网易云可以并发执行了,也即可以一边上qq,一边网易云,呜呜呜~
一边网易云,一边qq,这个问题解决了。但新的问题又来了。
刚开始,qq只有一个文字聊天功能,后来功能不断丰富,可以一边文字聊天一边视频或传输文件。
但是,问题来了,此时没有线程这个东西。所以实际的情况下,在qq当中,当你处于文字聊天,你就不能视频,或者视频时,你不能传输文件。狗子心理憋屈啊,qq功能如此丰富了,但是功能却不能同时使用,只能串行地使用。 如果qq里面也能同时使用这些功能就好了。就像一边上qq,一边网易云~
引入线程!
进程可以并发,进程内的线程也可以并发;就像qq和网易云可以同时运行,qq中 的视频和传输 文件也可以同时 运行
从此 ,进程是操作系统资源分配的单位,但调度单位 变成了线程
线程可以完全 类比进程去学习。
想象:一个IDEA进程 同时run 多个程序,就是多线程的鲜活例子
挑几个比较好的吧
各线程可以占用不同的cpu
线程几乎不拥有系统资源
线程间通信无需系统干预!
系统开销
2.1.6 多线程模型
本节导览
本节解决的问题
- 线程的实现
- 多线程模型
用户级线程 —— 弱智的while 循环 —— 优缺点 —— 早期unix
不涉及cpu 变态;快啊;但是堵塞也是真的慢啊
内核级线程
需要处理及切换到核心态 来 管理线程
内核级线程的多线程模型
- 一对一
- 多对一
- 多对多 中庸之道
用弱智的while 循环来实现 用户及多线程
灵魂四问!
- 用户
- 不需要
- 不能
- 优点:线程切换不需要操作系统的介入,开销很小;缺点:一旦某个线程阻塞,所有线程均不能正常执行。
优化优化!!!
内核级线程
- 由操作系统管理
- 需要
- 可以
- 优点:每个线程都可以拥有一个核,并发能力强,会堵塞;缺点:需要变态,系统开销大
一对一
一个用户级线程映射到一个内核级线程
优点:并发能力强;缺点:开销大,一对一很奢侈的
多对一
多个用户线程映射到一个内核级线程(其实和最开始的用户级线程差不多的)
优点:开销小,不需要变态 ;缺点:会阻塞
多对多
中庸之道
仔细体会:只有获得“运行机会”的一段“代码逻辑,才有机会被处理及执行。
2.1.7 线程的状态与转换
完全类比进程的状态转换三个
就绪 运行
阻塞
PCB vs TCB
下处理机
上处理机
2.2.1 调度的概念和层次
调度就是指挥干活
为什么调度分层次?类比军队的规模和调度的规模
低级调度——进程调度(内存-> cpu) 发生频率最高
中级调度——内存调度(外存->内存), 挂起状态(暂时被调到外存)
挂起状态——就绪挂起和阻塞挂起 5 + 2=7 种状态
高级调度——作业调度 (外存 ->内存)
本节导览
调度的概念
说白了,就是怎么合理地安排资源
高级调度
外存到内存,对象是作业,颗粒度比较大
低级调度
内存到cpu,对象是进程,颗粒度较细。从就绪队列中取进程,将处理机分配给它
中级调度
挂起状态和挂起队列由此而来
进程地挂起态和七模型
7 = 5 + 2
2 = 就绪挂起 + 阻塞挂起
挂起和阻塞的区别
进程被挂起,进程被调到外存去了
进程阻塞,此时还在内存中。
可以感性地想象:如果进程只是短暂地得不到满足,则是阻塞态;如果长时间得不到满足,干脆把它放到外存算了,别占着内存空间。
越低级,发生频率越高。
2.2.2 进程调度的时机 切换与过程 调度方式
调度时机
切换
方式
调度时机
- 适合时机
- 不适合的时机
蒙蔽概念:临界区,临界资源,操作系统内核程序临界区 看那道真题。。。真蒙蔽
进程调度的方式
- 剥夺
- 非剥夺
进程的切换
- 狭义
- 广义
本节导览
进程调度,就是上一节学的低级调度
合适时机
- 主动放弃
- 被动放弃
不行的时机
- 处理中断
- 原语
- 操作系统内核程序临界区(注意:这里特别说明了是内核的;那就意味着还有普通的临界区,然而,普通临界区是可以进行调度的)
上一道真题来剖析临界区这个概念
补充:临界区和临界资源对应起来的;临界区是访问临界资源的代码片段,既然临界资源要互斥地访问,那临界区也应该互斥地访问。
前者:就绪队列是内核的程序临界资源
后者:打印机是 普通的临界资源
前者会影响到操作系统的正常工作,后者不会。所以前者不能处于进程调度的时机,后者可以。
进程调度的方式
非剥夺式,即让当前进程心安理得地下处理机
剥夺调度,当有老大要来的时候,得给老大上处理机器
进程调度与进程切换
简单地说,进程调度或切换,其实就是确定下一个要上处理机的进程是谁。
进程切换所要完成的事情:1. 保存原来的数据 2.拿出新进程的数据
进程切换有代价,过于频繁,反而会降低系统效率(时间不花在正事——执行上)。
2.2.3 调度器与闲逛进程
调度器(调度程序) 类比,监听器(监听程序) xxx器,其实就是一段程序嘛
调度器要解决的问题:
- 让谁运行
- 运行多久
下面的不是调度器要解决的:
何时运行;何时不能运行
调度方式
idle 闲逛(进程)
操作系统很无聊,让idle 来闲逛一下,优先级最低
调度器/调度程序(scheduler)
调度程序处理的是②和③哦!
- 让谁运行 ?调度算法
- 运行多久 ? 时间片大小
调度时机——什么时间 会触发”调度程序“?
- 创建新进程
- 进程exit
- 运行进程阻塞
- IO 中断发生,唤醒某些阻塞的 进程,它们将假如就绪队列中
方式
- 非抢占式: 只有运行进程阻塞或 exit 才会 触发调度程序来干活 (比较优雅)
- 抢占式:每个时钟中断或k个时钟中断后 就会触发调度程序 (比较着急 )
调度算法处理的对象:有线程则是线程,没线程则是进程。
处理机的备胎伙伴——闲逛进程
反正处理机就不能闲着。
2.2.4 调度算法的评价指标
有哪几个评价指标
5个,慢慢看吧
本节导览
cpu 利用率
cpu 实际忙碌时间 / 总时间
要尽量压榨cpu 来干活
系统吞吐量
单位时间内完成的作业的数量
xx道 / 秒
周转时间
作业
从提交到完成的时间
平均周转时间
带权周转时间 平均带权周转时间
带的权是什么?是作业实际运行的时间。
等待时间
分为进程等待时间和作业等待时间
作业等待时间还需要加上作业在外存后备队列中等待的时间
调度算法只会影响作业/进程的等待时间
提交请求到首次产生响应的时间
2.2.5 调度算法1
调度算法的学习思路?
- 思想
- 规则
- 算法针对的对象?作业调度还是进程调度
- 抢占or非抢占
- 优缺点
- 饥饿与否?(进程/作业 长时间得不到服务,饿死了)
用上一节课学到的调度算法的评价指标来评价各种算法的优劣
算法是硬骨头,得啃下
本节总览
FCFS 先来先服务算法
思想:公平
规则:按照到达的先后顺序进行服务
非抢占
优缺点
饥饿:不
先来先服务 等价于 等待时间越久越优先被服务(好好想清楚,先来的话不就得等久一点嘛)
周转时间 = 完成时间 - 到达时间
带权周转时间 = 周转时间/运行时间
等待时间 = 周转时间 - 运行时间平均xx 时间 = xx 时间/作业数量
对FCFS的算法评价
优点:公平,简单
缺点:对长作业有利,对短作业不利。排队买奶茶(第一个人 买30杯,第二个人买一杯,导致第二个人被迫为了一杯奶茶而等待半小时)
不会饥饿
短作业优先法 SJF
有抢占式和非抢占式之分
抢占式成为最短剩余时间优先法
短进程优先法 SPF
最短剩余时间优先法 SRTN
SPF 和 SRTN 对比
算法评价
高响应比优先 HRRN
能不能综合FCFS 和 SPF 的优点呢?
高响应优先 HRRN 来也!
注意看相应比,等待时间和服务时间都用上了
算法评价
2.2.6 调度算法2
本节的调度算法2,视角和算法1的不一样。算法1着重分析各算法的评价指标(周转时间、等待时间等指标)
本节注重现代操作系统(分时OS)下的调度算法的研究。
虽然着眼点不同,但是这两节内容仍然是有联系的。比如本节的时间片调度算法中,当时间片过大时,会退化为FCFS。
本节总览
还记得吗?学习各种调度算法的学习思路?
- 算法思想
- 规则
- job or process
- 抢占式or非抢占式
- 优缺点
- 饥饿与否?
时间片轮转
基于分时系统。时间片
注入队头队尾的插入
抢占式!时间到则强制下机
自己分析:
p1 p2 p1 p2p3 p4 p1 p4 p4
0 - 2 - 4 - 6 - 8-9 - 11-12 - 14 - 16
- p1(3)
- p2(2) p1(3)
- p1(1) p2(2) p3(1)
- p2(0) p3(1) p4(6) p1(1)
- **P3(0) **p4(6) p1(1)
- **p4(4) **p1(1)
- p1(0) P4(4)
- p4(2)
- p4(0)
** 默认:新到达的比刚下处理机的更靠近队头**
p1 p2 p1 p3 p2 p4 p1 p4 p4
0 - 2 - 4 - 6-7 - 9 - 11-12 - 14 - 16
- p1(3)
- p2(2) p1(3)
- p1(1) p3(1) p2(2)
- p3(0) p2(2) p4(6) p1(1)
- **P2(0) **p4(6) p1(1)
- **p4(4) **p1(1)
- p1(0) P4(4)
- p4(2)
- p4(0)
** 以上我的记法和咸鱼的不太一样**
我每一行首进程括号里记录的是(执行完当前时间片后剩余的执行时间);而咸鱼的记法是记录该时间片执行前的剩余执行时间。
时间片为 2
时间片为 5
自己分析时间片为5的情形
p1 p2 p3 p4 p4
0 - 5 - 9 - 10 - 15 - 16
换另一种记法(执行前剩余执行时间,本次执行花费的时间)
- p1(5,5)
- p2(4,4)
- p3(1,1)
- p4(6,5)
- p4(1,1)
时间片轮转算法的评价
时间片到了,需要切换进程,进程切换的开销也是重要的衡量指标
努力使切换进程的开销占比低于1%
辩证地看待时间片的过大与过小
时间片过大 ,则退化为 FCFS,对长作业有利,对短作业不利
时间片过小,频繁切换进程,系统开销大
优先级调度
优先级调度算法有抢占式与非抢占式之分
非抢占式:当前进程主动放弃处理机时发生调度
抢占式:当前进程主动放弃处理机时发生调度 + 就绪队列发生改变时判断是否需要调度
非抢占式
操作步骤还是比较简单的
抢占式
抢占式的调度时间节点会比非抢式的更多,所以需要考虑多一点
优先级调度的算法评价
静态与动态优先级
进程优先级的设置依据(系统 > 用户; 前台 > 后台)
操作系统更偏好 I/O繁忙型进程(I/O 尽早开始工作,越好)
动态优先级的考虑因素:公平、资源利用率、饥饿、IO 频繁程度
会饥饿
多级反馈队列(各算法优点的集大成者)
有没有集大成者?
有!你爹来了!
过程较为繁琐,细品(请配合视频课的动画去理解)
2.2.7 调度算法3
多级队列调度
就一张ppt
分类的思想
- 优先级的分类(高 到 低)
- 时间片在不同优先级的分配
- 优先级固定与否 (是否要等到高优先级的全都执行完才可以执行低优先级的?)
- 队列中可采用不同的调度策略
可以很灵活
2.3.1 进程同步、互斥
概念
举例
理解
本节总览
先概述视频课中两个生动形象地例子(进程同步)
- 老渣和一号二号两位女嘉宾约会。一号想成为老渣的初恋,二号想找有恋爱经验的人。这就要求老渣得先把心交给一号,再把心交给二号。这两步顺序不能颠倒。
- 进程间的管道通信,先得往管道里写数据,才能从管道中读数据,顺序不能颠倒
以上两个例子都是进程同步涉及的问题
进程互斥的场景举例
- 打印机摄像头资源的互斥共享(临界资源的访问 )
提问:进程同步和进程互斥的区别? 他们对应了哪些具体的场景?
答:进程具有异步性,但为了让进程按照我们的意愿去执行,我们得想法设法让进程同步,所以进程同步解决的是进程异步的问题;进程互斥是指不同进程访问同一临界资源,要互斥地访问,不能同时同时一起访问。
进程同步
进程互斥
临界资源的概念:同一个时间段内,只允许 一个进程使用的资源
对临界资源的访问:互斥
进程互斥:当一个进程访问某临界资源时,另一个进程也想访问该临界资源,则必须等待。
进程互斥的四个区
进入区:检查是否可以进临界区:可进,则设置正在访问的标志位(上锁),以告知其他进程不给进
临界区:访问临界资源的那段代码
推出区:解除 正在访问的标志位(解锁)
剩余区:做其他处理
进入区和退出区时负责实现互斥的代码段
进程互斥的原则
- 空闲让进
- 忙则等待
- 有限等待
- 让权等待 (防止自旋等待,忙等待,不然会白白占用处理机)
2.3_2_进程互斥的软件实现方法
谦让和表达意愿
压岁钱!
能否一气呵成
多想想并发的执行会导致什么问题?比如死锁或都可以访问
解决了啥问题,解决不了啥问题?为什么
本节总览
实现互斥的四个逻辑部分:
- 进入区
- 临界区
- 退出区
- 剩余区
互斥四原则
- 忙则等待
- 空闲让进
- 有限等待
- 让权等待
打印机脑抽
单标志位
只在前面设置一个turn,用来标识可以允许几号进程访问
各进程中有while 循环来不断检查自己能否进入
为什么说违反了“空闲让进”原则?
解释:假设P0 进入临界区,执行完后将turn 置为 1。P0还想再进入临界区,但P1进程一直都不进入临界区,导致turn 的值一直是1,P0想再进一次都不行 。一定要等对方将turn 修改为属于我的turn 才可以。
简单地说,就是我想进临界区之前,还得对方同意。对方要是一直不同意,既是临界资源空闲,我也不能访问。
双标志先检查法
不服就加标志!
搞一个数组,将两个线程的意愿都存进去
访问临界区时修改数组值,访问完也要修改
为什么说违反了“忙着等待”原则?
终究时扛不住并发的考验!检查和上锁不是一气呵成!
精辟:在“检查”后,“上锁”前有可能发生进程切换。
双标志后检查法
双标志前检查法,会导致两个进程同时进入临界区。这是因为在“检查”后,“上锁”前有可能发生进程切换。
因此,想到先“上锁”后“检查”来试试
while是检查,数组赋值是上锁
你终归是扛不住并发的考验!
虽然解决了”忙则等待“问题,但又违背了“空闲让进"和“有限等待”
死锁,饥饿
Peterson 算法
到底有没有好方法啊!!
Peterson 来也!
单独是turn 或单独用数组都不行,那我两个都用!!!
用数组来表达意愿;用turn 来表达谦让
while 循环条件是 对方的意愿和turn 综合的结果 (只有对方有意愿而且我谦让了,我才会阻塞,给对方执行)
看最后一次说客气话(改变turn 的值)是谁!
现在就只剩让权等待实现不了
2.3_3_进程互斥的硬件实现方法
让权等待
单处理机?多处理机?
一气呵成?
原理类似与否?
时空想象!
中断屏蔽方法
只适用于单处理和操作系统内核进程,因为开/关中断指令只能运行在内核态
开关中断是对一个核而言的
TestAndSet 指令 (TS or TSL)
要有一定的时空想象
while循环执行得很快!!!一直在试探能不能抢到锁。一旦检测到old 为false,就立马强到锁。
TSL是硬件实现得,这个函数可以 一气呵成!!(实现逻辑和软件一样,但是软件做不到一气呵成)
缺点:还是未能解决”让权等待“这个问题!!
Swap 指令
如果lock 和 old 交换后,old 为false,则说明lock 为false,即解锁了,此时会推出循环,执行临界区代码。
逻辑上和TSL 差不多
2.3_4_互斥锁
自旋锁 忙等待 让权等待? 占用处理机?
上锁时间短的话
自旋等待,并不一直占用cpu 资源,时间片到了,会被下机。
不适用于单处理机,因为忙等的过程不可能解锁?
2.3_5_信号量机制
让权等待!!!之前一直解决不了!
软件解决方案 vs 硬件解决方案
一对原语 wait (P) signal(V)
P V 操作 西班牙语
以前的所有方法都无法解决”让权等待“的问题
原语+软件 = 可以解决”让权等待“
整形信号量
打印机例子
关键是使用了原语机制
还是会导致“让权等待”呀!会发生忙等!
还是会发生忙等?因为 wait() 的while 循环会一直循环
记录型信号量
好处多多,应用场景也很多;同步和互斥都可以用到记录型信号量
整形信号量会忙等
记录型信号量不会忙等呜呜呜
记录型信号量绝佳!!!好贴心!好有逻辑!自己爽完还会唤醒等待队列
得不到满足的进程会转成阻塞态,排到等待队列中,不会在那傻傻地忙等
记录型信号量的定义!typedef struct
block()会导致阻塞而不是忙等
wakeup() 唤醒
这个打印机的例子,最好配合视频的动画来理解会更清晰
要认真感受和想象对应的情形
2.3_6_用信号量实现进程互斥、同步、前驱关系
信号量对应资源
信号量的值对应资源数量 大于零 小于零 的含义
semaphore的数据结构
阻塞态
mutex 的值
PV 操作是成对的
进程同步是要干啥;同步关系 ,关键是要分清前后关系,一前一后
如果利用信号量机制实现
利用PV对资源S的操作来控制一前一后地执行
S的数值变化
同步操作:前V 后P;V+ P-
PV 操作有可能唤醒别的进程!妙啊
互斥操作初始值为1
同步操作初始值为0
前驱问题其实是多个同步问题的集合
信号量的含义
信号量的value
P(S) 申请一个资源,会导致 S.value -- ** 如果资源不够就阻塞等待(不是忙等哦)**
V(S) 释放一个资源,会导致 S.value ++ 如果没有进程在等待该资源,就wakeup 一个进程
- 界定临界区资源
- 设置互斥信号量mutex 的值
- 进入区 P(mutex) —— 申请资源
- 退出区 V(mutex) —— 释放资源 (同步问题需要先执行的代码释放资源,后执行的代码才能执行)
注意:
- PV 操作成对出现
- 缺少P 则不能互斥访问
- 缺少V 则有可能不会唤醒别的进程
- 对不同的临界资源需要设置不同的互斥信号量,mutex1(摄像头) mutex2(打印机) mutex3(麦克风)
信号量机制实现进程同步
进程同步问题的实现:后执行的代码需要得到前执行代码释放的资源(V(S)) 才行
信号量机制实现前驱关系
其实是多级的同步问题
2.3_7_生产者-消费者问题
生产者消费者问题
解决进程同步问题
生产者 缓冲区(容量为5) 消费者
缓冲区满,生产者阻塞;缓冲区空,消费者阻塞
这是同步问题
一前一后为同步,不能同时为互斥
多个生产者,多个消费者的场景呢?有可能同时操作同一块空间
这是互斥问题
确定PV 操作的顺序来解决上述 问题
什么时候/条件执行P,什么时候/条件执行Q
再设置合理的初始值:mutex 互斥信号量;full ; empty(这一步的初始值设置还是绕的,是0 1 还是n,设置的依据是什么得看具体场景)
互斥信号要设置为mutex,处置一般为1;同步信号按照资源名称来设置
难点:P操作谁?V操作谁?多重PV怎么合理安排?要跳出代码考虑具体的情境才能更好地把握
PV一定要闭合
前驱图 V 携带资源指向 P
能否改变相邻的PV 操作顺序?(要分清是同步的PV还是互斥的PV)
循环等待 死锁的问题
结论:
- 实现互斥的P操作一定要在实现同步的P操作之后,不能颠倒,否则死锁;V操作的顺序 可以交换。
- 临界区的代码最好不要放到PV操作里面(考虑性能)
紧紧抓住两个量:产品数量与空闲位置(这是两种资源)
注意:**否则必须等待 **
自己分析前驱关系
前驱关系图
只能说这里的full 和 empty ,起这种名字真的是表意不明,还让人抓狂搞不懂!到底谁full,谁empty;什么时候full什么时候empty 啊!
设置为 product 和 place 不挺好的吗?
p减v加
生产者对产品数量是V 对空闲位置是P
消费者对产品数量是P 对空闲位置是P
一开始,空闲位置为n,产品为0
生产者进程和消费者进程还要实现互斥访问
要看懂这个前驱图啊,并不是什么双向的图。分析:有产品(V 提供产品资源)才可以消费P(消耗产品资源);缓冲区没满(V 提供空闲位置资源)才可以让生产者生产(P 消耗空闲位置资源)
full 和 empty 在这里是什么含义? 结合这张图,我猜,full 是缓冲区中的产品数量,empty 是缓冲区中的空域位置数量。一开始,full = 0 ,empty = n
和这个图的分析是差不多的
full 和 empty 在这里是什么含义? 这页ppt 右下角不是有解答吗,前面没发现,真是不应该啊,还自己猜呢,不过也没猜错。
分别分析生产者和消费进程和互斥访问
生产者:
- 生产产品(消耗空闲缓冲区)
- 把产品放入缓冲区(增加产品)
消费者:
- 从缓冲区取出一个产品(消耗产品 )
- 使用产品(增加空闲缓冲区)
以上的顺序不要弄乱
互斥访问
也要提供PV操作
仔细想象对应的场景!很重要!
当没有空位时,Producer执行了P(mutex),Consumer紧接着也执行了P(mutex),Consumer会因为mutex 而阻塞。Producer执行完P(empty)后被阻塞,因为没有空位置,只能等待Consumer消费一个产品腾出一个空位置,但是Consumer 在mutex 就已经被阻塞了,无法腾出空位置。
现在的局面就是,Producer 需要 空位置,但得不到满足,但又不肯解锁;Consumer 因为没有获得锁,而没办法腾出空位置。相互僵持的局面,谁也不肯退让。
前驱图!
同步!
互斥!
设置信号量!
死锁问题!
2.3_8_多生产者-多消费者问题
本节提示
有哪些进程
有哪些事件
哪些进程存在同步关系;哪些进程存在互斥关系
思考同步问题的颗粒度?着眼点是“事件”,而非单个进程本身,一个事件可以牵扯很多个进程。
餐盘只能装一个水果
爸爸往餐盘放苹果;妈妈往餐盘放橘子
女儿从餐盘取苹果;儿子从餐盘取橘子
自己分析 有哪些进程?同步关系?互斥关系?PV 操作
- 有四个进程
- 考虑同步关系
- 爸爸放了苹果,女儿才能拿苹果。一前一后
- 妈妈放了橘子,儿子才能拿橘子
- 考虑互斥关系
- 不能同时访问餐盘
- PV 操作和PV 前驱图
- 明确有哪些资源
- plate 餐盘空闲容量,初试是1
(进程互斥问题,初始值为1)(这里的plate是指盘中可以放多少水果,而不是互斥问题)- 苹果数量 apple,初始是0
- 橘子数量 orange,初试是0
- 还有互斥变量mutex要设置
- PV 操作的安排
- 对于plate 餐盘容量,爸爸和妈妈需要在while 循环中 P(plate),对餐盘进行P 操作;女儿和儿子取走水果后对餐盘进行 V(plate)操作
- 爸爸往餐盘放apple V(apple),女儿从餐盘取apple P(apple)
- 妈妈往餐盘放orange V(orange),儿子从餐盘取orange P(orange)
同步:前V后P
注意同步的P 要在互斥的P 前
提问:可不可以不用互斥信号量?
答:此处可以,因为mutex = 1;
容量为2,则可能进程同时访问同一块空间
思路整理
同步问题的思考粒度——以事件为着眼点
2.3_9_吸烟者问题
取模
P阻塞 V给资源
同步问题
三个抽烟者进程,一个供应者进程
每个抽烟者都缺一个独特的组合;供应者每次提供一个独特的组合
抽烟者抽完烟会给提供者一个信号
同步问题
- 吸烟者须得到供应者提供给它的组合,才能抽烟(一前一后)
- 供应者接收到吸烟者吸完的信号后,才继续提供组合(一前一后)
互斥问题
此处暂不考虑互斥问题
同步问题,先V 后P
是否需要专门设置一个互斥的信号量,这个问题多生产多消费者的时候遇到了,如果多个进程最多只有一个可以访问缓冲区,则不需要。
2.3_10_读者写者问题
本节专注于互斥问题
本节的思想精华是设置互斥变量来实现一气呵成的效果
- 允许多个读者同时对文件执行读操作
- 只允许一个写者执行写操作
- 任一写着在完成写操作之前都不允许其他读者或写着工作
- 写着执行完写操作前,应让已有的读者和写着全部退出
本节主要分析读写进程的互斥关系。
互斥信号量初值一般设为1
互斥关系:写-写;写-读
这里没加mutex 互斥变量会出现没办法同时读的问题;因为对count 的检查和赋值 无法一气呵成(所以需要增加互斥变量 mutex)
写进程饿死
再次添加一个互斥变量w,来解决写进程饿死问题
“读写公平法”
思想的升华
2.3_11_哲学家进餐问题
哲学家问题和前面的不同之处在于,该问题涉及一个进程同时请求两个临界资源
死锁原因
看看2号哲学家,虽然他本可以拥有两个筷子,但还是被阻塞了!这就是这个解决方案的关键所在
更准确的说法!
2.3_12_管程
引入管程,方便程序员实现进程/线程的互斥、同步
思想:封装的思想,封装成数据结构、类、方法等;然后对外暴露接口。用函数封装了最原始的PV 操作,程序员只需调用函数即可,进程互斥、同步由编译器来处理
实战:用管程来解决生产者消费者问题;java 中 的 synchronize 关键字
如果按最原始的方式来实现同步、互斥,则需要合理地安排各种PV 操作,容易出错,且效率低。
管程的定义类似java 的类,类中有数据结构(定义的数据类型)和函数(过程)
数据结构——自定义的数据类型
一组过程——一组函数
初始值——初始化变量
管程名字——类名
管程的基本特征
- 管程中的数据只能由管程中的过程所访问。(private 变量,只能由类中的private函数操作)
- 一个进程只有通过调用管程中的过程才能进入管程访问共享数据(进程调用类中对外暴露的接口,才能间接访问管程的共享数据)
- 每次仅允许一个进程在管程中执行某个内部过程(由编译器实现)
相当于定义了一个类 ProducerConsumer。
类中有一些条件变量 full、empty,控制变量 count
有函数 insert() 和 remove()
函数中封装了 PV 操作
注意:每个函数中都有signal (),即V 操作,当条件满足时,执行V 操作可以唤醒因等待资源而被阻塞的进程
- 如 insert() 函数中,if (count == 1) ,即说明该生产者生产该产品前,缓冲区是空的,它是缓冲区空了之后第一个放产品进缓冲区的,这时对empty 执行signal() 即V 操作来唤醒可能正在等待产品的消费者进程。注意:insert() 函数中,为什么是count ==1 ,而不是count ==0 ,因为
count++
在if (count == 1)
前执行- remove() 函数中, if (count == N -1) ,即说明该消费者取出这件商品前,缓冲区是满的,这时对full 执行 signal() 即V 操作来唤醒可能正在等待空位置的生产者进程。注意:remove() 函数中,为什么 count ==N -1,而不是N呢?因为
count--
在if (count == N -1)
前执行
2.4_1_死锁的概念
吃着碗里的,盯着别人碗里的。
退一步海阔天空,但没有人退
死锁,进程争抢资源,不肯退让,导致进程阻塞
饥饿,进程长时间得不到执行
死循环:自己写的无限循环,停不下来,在原地旋转跳跃,闭着眼,傻嗨
死锁的四个必要条件
四个条件都是必要条件
互斥条件:争夺同一个资源
不剥夺条件:进程已经持有的资源,只有进程自己可以主动释放
请求和保持条件:吃着碗里的,还要抢别人碗里的
循环等待条件
发生循环等待时,如果有一个持有相同资源的局外人愿意贡献自己的资源,那么可以解决循环等待导致的死锁
常见的发生死锁的时机
可以类比如何防止工厂火灾:1. 思考起火的必要条件,尽量破坏起火的必要条件:取出可燃物,隔绝氧气等 2.通过合理的工厂布局 3.即使着火了,及时将火灾掐灭在火苗阶段
预防死锁:破坏死锁的必要条件 (静态)
避免死锁:算法支撑 (动态)
死锁的检测和解除:检测+解除
2.4_2_预防死锁
破坏死锁的四个必要条件
前面哲学家进餐问题那一节给出了三个解决死锁的方案,请学完本节后思考那三个解决方啊分别对应了哪些必要条件
破坏互斥条件
把互斥资源做成非互斥资源,这里应用了SPOOLing技术,把独占设备在逻辑上改造成共享设备
缺点:系统安全问题
破坏不剥夺条件
不剥夺条件:等进程自己主动释放
破坏不剥夺条件可以避免死锁
- 方案一:请求不到,则释放持有的全部资源
- 方案二:考虑优先级,让操作系统强行剥夺。比如之前学的剥夺调度方式
缺点:
- 实现复杂
- 释放已有资源,会导致工作记忆丢失
- 反复申请和释放资源,系统开销大
- 方案一:可能导致进程饥饿
破坏请求和保持条件
请求和保持条件:吃着碗里的,盯着别人碗里的
静态分配法:一次申请完所需的全部资源,归他所有。
缺点:资源浪费问题;进程饥饿问题
破坏循环等待条件
循环等待条件:循环链,你爱她,她爱他,他爱我。
顺序资源分配法:顺序编号,不能吃回头草(不能回头申请小编好资源)
缺点:
- 不方便增加新的资源,涉及重新编号
- 申请资源的顺序和使用资源的顺序不一致,导致资源浪费。(如申请时:先申请5号,再申请7号;使用时:先使用7号,再使用5号)
- 必须按次序申请资源,用户编程麻烦
2.4_3_避免死锁(银行家算法) 重点
银行家算法是重点!!!
一定要搞清楚搞明白!
安全序列:按某种规则分配资源后,还能顺利回收。这种规则下得到的分配序列就是安全序列
安全序列可以不唯一
不安全序列只是有可能发生死锁,并非一定。但发生死锁,则一定是不安全序列。
引入向量
银行家算法的代码实现
2.4_4_死锁的检测和解除
如果能结合数据结构的图结构会更好
本节内容还挺有趣
既不阻塞又不是孤点?
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构