文章目录
一、死锁
由于竞争资源或者通信关系,两个或更多线程在执行中出现,永远相互等待只能由其他进程引发的事件.
1.必要条件
- 互斥
任何时刻只能有一个进程使用一个资源实例。 - 持有并等待
进程保持至少一个资源,并正在等待获取其他进程持有的资源。 - 非抢占
资源只能在进程使用后自愿释放。 - 循存等待
存在进程集合{P0,P1,…,PN} ,P0正在等待P1所占用的资源,P1 正在等待P2占用的资源,…,PN-1在等待PN所占用资源,PN正在等待P0所占用的资源。
(就是说死锁出现后一定会出现以上四种情况,但是满足以上四种情况出现不一定会导致死锁,只是可能哦。而如果不满足以上四种,就肯定不会导致死锁。 纠正了我的认知 QAQ 。🤡)
2.死锁处理办法
(由强到弱 ↓)
-
死锁预防:
确保系统永远不会进入死锁状态。
限制申请方式:预防是采用某种策略,限制并发进程对资源的请求,使系统在任何时刻都不满足死锁的必要条件。 -
死锁避免:
在使用前进行判断,只允许不会出现死锁的进程请求资源。
用额外的先验信息,在分配资源时判断是否会出现死锁,只在不会死锁时分配资源。
(1)要求进程声明需要资源的最大数目
(2)限定提供与分配的资源数量,确保满足进程的最大需求
(3)死锁避免算法动态检查的资源分配状态,确保不会出现环形等待 -
死锁检测
-
死锁恢复
允许系统进入死锁状态,在检测到运行系统进入死锁状态后,进行恢复。 -
忽略这个问题,假装系统中从来没有发生死锁,用于大多数操作系统,包括 UNIX。
(出现了环形等待,不一定出现死锁,只是一种 不安全状态。∴ 不安全状态 > 死锁状态,我们需要的是安全状态👾 。)
安全状态
针对所有已占用进程,存在安全序列:
序列 <P1,P2,…,PN> 是安全的,Pi要求的资源 ≤ 当前可用资源+所有Pj 持有资源,其中 j<i。如 Pi 的资源请求不能立即分配,则 Pi 等待所有Pj(j<i)完成,Pi完成后,Pi +1可得到所需资源,执行并释放所分配的资源。最终整个序列的所有 Pi 都能获得所需资源。
- 安全状态与死锁的关系
(1)系统处于安全状态,一定没有死锁
(2)系统处于不安全状态,可能出现死锁
(3)避免死锁就是确保系统不会进入不安全状态
3.银行家算法 Banker’s Algorithm
银行家算法是一个避免死锁产生的算法。以银行借贷分配策略为基础,判断并保证系统处于安全状态。
在客户贷款数量不超过银行拥有的最大值时,银行家尽量满足客户需要。
客户在第一次申请贷款时,声明所需最大资金量,在满足所有贷款要求并完成项目时,及时归还。
其中,银行家类比为操作系统;资金类比为资源;客户类比为 申请资源的线程。
-
前提
(1)多个实例。
(2)每个进程都必须能最大限度地利用资源。
(3)当一个进程请求一个资源,就不得不等待。
(4)当一个进程获得所有的资源就必须在一段有限的时间释放它们。
基于上述前提条件,银行家算法通过尝试寻找允许每个进程获得的最大资源并结束 (把资源返还给系统)的进程请求,来决定一个状态是否是安全的。
不存在这满足要求的执行时序的状态都是不安全的。 -
数据结构
n = 线程数量, m = 资源类型数量
💕Max(总需求量): n×m 矩阵。
Max[i,j] = k 表示进程 Pi 最多请求 资源类型 Rj 的 k 个实例。
💕Available(剩余空闲量):随着运行过程动态变化的向量。
长度为 m 的向量。 Available[j] = k 表示当前有 k 个类型 Rj 的资源实例可用。
💕Allocation(已分配量):n×m 矩阵。
Allocation[i, j] = k 表示线程 Ti 当前分配了 k 个 Rj 的实例。
💕Need(未来需要量):n×m 矩阵。
Need[i, j] = k,线程 Ti 未来需要 k 个Rj 资源实例。
💕💕💕Need[i,j] = Max[i,j] – Allocation[i,j] 。 -
判断安全性的步骤
1.定义向量 Work 和 Finish,Work 和 Finish 分别是长度为 m 和 n 的向量初始化:
//当前资源剩余空闲量
Work = Available
//Finish 表示当前进程是否结束。
//如果 Finish[i]=false,说明进程还没有结束,可能在 sleep;
//如果 Finish[i]=true, 说明所有资源都得到了,时间到了就会释放,正常执行并结束。
//一开始都是 false,都没有结束,都在申请资源中。
Finish[i] = false for i:1,2, …, n.
2.寻找线程 Ti,需要满足以下条件:
//进程还未结束
(a) Finish[i] = false
//接下来找出 Need 比 Work 小的线程 i
//这样的进程,对资源的需求,可以通过向系统空闲资源发出请求得到满足。
(b) Need[i] ≤ Work
//线程 i 的资源需求量小于当前剩余空闲资源量, 所以配置给它再回收
Work = Work + Allocation[i]
Finish[i] = true
转2.
如所有线程Ti满足Finish[i] == true
//所有线程的Finish为True,表明系统处于安全状态
则系统处于安全状态
- 银行家算法
(1)初始化:
Requesti 线程Ti的资源请求向量
Requesti[j] 线程Ti请求资源Rj的实例
(2)循环
① 如果 Requesti ≤ Need[i], 转到 步骤②。
否则, 拒绝资源申请, 因为线程已经超过了其最大要求。
② 如果 Requesti ≤ Available, 转到步骤③。否则, Ti 必须等待, 因为资源不可用。
③ 通过安全状态判断来确定是否分配资源给Ti : 先生成一个需要判断状态是否安全的资源分配环境。
Available = Available -Requesti;
Allocation[i]= Allocation[i] + Requesti;
Need[i]= Need[i]–Requesti;
调用安全状态判断,如果返回结果是安全,将资源分配给 Ti ;如果返回结果是不安全,系统会拒绝 Ti 的资源请求。·
👀 例子 1:
该状态是否是安全的呢?假设 T1-T4 4个进程都没有结束,Available 是 (0,1,1),那么 Work 就等于 (0,1,1),需要寻找 小于 (0,1,1) 的,可以看到 T2 (0,0,1) 符合条件,所以 T2 的 Finish 设置为 true,而且需要把 T2 的资源释放掉,返回给 Available。所以,T2 完成运行后,:
接下来 ,能够 finish 的线程 是 T1,T1 释放的资源就能返回到 Available:
再下来,T3 符合条件:
同理,T4也是符合条件的,这样的话,所有的线程都可以顺利执行并结束,它们需要的资源都能得到满足。这样就是安全状态,可以得到安全的序列: T2 --> T1 --> T3 --> T4 。
4.死锁检测算法
(基于安全状态判断算法。定期执行该算法,判断是否成环。)
💕Available:长度为 m 的向量,表示每种类型可用资源的数量。
💕Allocation:一个 n×m 矩阵。当前分配给各个进程每种类型资源的数量, Allocation[i, j] = k 说明进程 Pi 拥有 k 个 资源 Rj 的实例。
💕Request :一个 n×m 矩阵。表示各进程的当前请求。 Request [i,j] = k,表示 进程 Pi 请求 k 个资源 Rj 的实例。
- 步骤
1.Work 和 Finish 分别是长度为m和n的向量初始化:
//work为当空闲资源量
(a)Work = Available
//Finish 为线程是否结束
(b)Allocation[i]> 0 时, Finish[i] = false;
否则,Finish[i] = true
2.寻找线程 Ti 满足:
//线程没有结束的线程,且此线程将需要的资源量小于当前空闲资源量
(a)Finish[i] = false
(b)Requesti ≤ Work
没有找到这样的i,转到4
//把找到的线程拥有的资源释放回当前空闲资源中
Work = Work + Allocation[i];
Finish[i] = true; 转到2
如某个 Finish[i] == false,系统处于死锁状态
算法需要O(m x n2) 操作检测是否系统处于死锁状态,开销比较大,而且需要定时知道每个线程的开销,这个开销是不易获得,所以死锁检测算法不常使用。
👀 例子:
首先 T0 是满足 ≤ Available 的,所以是可以先结束的,那么它拥有的 (0,1,0) 会加到 Available 中:
接下来, T2 ≤ Available …(重复如上操作):
然后,先选 T1 再选 T3 再选 T4,发现所有的资源都可以被满足,那就没有出现死锁喽。
何时、使用什么样的频率来检测依赖于 死锁多久可能会发生 和 多少进程需要被回滚。
5.死锁恢复算法
(1)方法一:进程终止
①终止所有的死锁进程。
② 一次只终止一个进程直到死锁消除
终止进程的顺序应该是 ↓:
- 进程的优先级
- 进程已运行时间以及还需运行时间
- 进程已占用资源
- 进程完成需要的资源
- 终止进程数目
- 进程是交互还是批处理
(2)资源抢占
选择一个受害者——最小的成本。
回滚——返回一些安全状态,重启进程到安全状态。
饥饿——同一进程可能一直被选作被抢占者。
二、进程通信(IPC, Inter-Process Communication)
进程通信是进程进行通信和同步的机制(进程间如何进行信息交互)。
IPC提供2个基本操作:发送操作:send(message) 和 接收操作:receive(message)。
通信方式分为两种:直接和间接。
间接通信:进程 A 将信息发送给内核,然后由内核 将 信息 发送给 进程 B。
(1)直接通信
- 进程必须正确的命名对方
① send (P, message) – 发送信息到进程P
② receive(Q, message) – 从进程 Q接受消息 - 通信链路的属性:
自动建立链路
一条链路恰好对应一对通信进程
每对进程之间只有一个链接存在
链接可以是单向的,但通常为双向的
(2)间接通信
- 通过操作系统维护的消息队列实现进程间的消息接收和发送
① 每个消息队列都有一个唯一的标识。
② 只有共享了相同消息队列的进程,才能够通信。 - 通信链路的属性
只有共享了相同消息队列的进程,才建立连接
连接可以是单向或双向
消息队列可以与多个进程相关
每对进程可以共享多个消息队列
阻塞和非阻塞通信
消息传递可以是阻塞 或 非阻塞,阻塞被认为是同步的,非阻塞被认为是异步的。
(??? 没懂。同不同步 阻不阻塞 在消息传递里是一回事儿莫?)
通信链路缓冲
进程发送的消息在链路上可能有3种缓冲方式
(1)0 容量:
发送方必须等待接收方。
(2)有限容量
通信链路缓冲队列满时,发送方必须等待。
(3)无限容量-无限长度
发送方不需要等待。
1.信号
进程间的软件中断通知和处理机制:如:SIGKILL, SIGSTOP, SIGCONT等。
应用程序接收到信号的处理:
(1)捕获(catch):
执行进程指定的信号处理函数被调用
(2)忽略(Ignore):
执行操作系统指定的缺省处理,例如:进程终止、进程挂起等。
(3)屏蔽(Mask):禁止进程接收和处理信号
可能是暂时的(当处理同样类型的信号) 。
2.管道(pipe)
设计 UNIX 的科学家任务每个程序应该完成一个单独的小的功能,然后通过 管道 🛢 把程序组合起来:把一个程序的输出重定向为另一个程序的输入,像流水线一样逐步往前走,就是通过 ”竖线“ | ,完成的管道机制。
👀例如:
% ls | more
shell 是一个进程,当它收到 ls | more
命令时,会先创建 ls、more 两个进程 (这俩进程有共同的父进程 shell,可以继承父进程的资源),同时把 ls 的 output 输出做特殊处理,把它输出到管道中,管道实际上是内存中的一块儿 buffer,|
兼具 输入 和 输出的功能,对于 more 是输入,从管道中取数据。 buffer 是有限的,如果 ls 输出将 buffer 填满了,会发生阻塞。
3.消息队列
消息队列是由操作系统维护的以字节序列为基本单位的间接通信机制。每个消息(Message)是一个字节序列,相同标识的消息组成按先进先出顺序组成一个消息队列(Message Queues)。
4.共享内存
共享内存是把同一个物理内存区域同时映射到多个进程的内存地址空间的通信机制。不需要系统调用,直接通过读写内存完成数据共享,方便、高效。但是必须用额外的同步互斥机制来协调数据访问。