【Chapter5】死锁与饥饿,计算机操作系统教程,第四版,左万利,王英

news2025/1/15 22:49:37

文章目录

    • 1.1 什么是死锁
    • 1.2 死锁的类型
      • 1.2.1 竞争资源引起的死锁
      • 1.2.2 进程间通信引起的死锁
      • 1.2.3 其他原因引起的死锁
    • 1.3 死锁产生必要条件
    • 1.4 死锁的处理策略
    • 1.5 死锁的预防
      • 1.5.1 破坏资源独占条件
      • 1.5.2 破坏不可剥夺条件
      • 1.5.3 破坏保持申请条件
      • 1.5.4 破坏循环等待条件
    • 1.6 死锁的避免
      • 1.6.1 什么是安全序列
      • 1.6.2 安全序列、不安全状态、死锁的联系
      • 1.6.3 银行家算法
      • 1.6.3.1 安全性检查算法
      • 1.6.3.2 资源分配算法
    • 1.7 死锁的检测与消除
      • 1.7.1 资源分配图
      • 1.7.2 资源分配图的约简
      • 1.7.3 死锁检测算法
      • 1.7.4 死锁检测时刻
      • 1.7.5 死锁的恢复
    • 1.8 鸵鸟算法
    • 1.9 饥饿与活锁
      • 1.9.1 饥饿与饿死
      • 1.9.2 活锁
    • 1.10 死锁的例子
      • 1.10.1 过河问题1
      • 1.10.2 过河问题2
    • 1.11 可复用资源的静态分析
      • 1.11.1 相关概念
      • 1.11.2 可复用资源死锁的静态分析算法
    • 1.12 同种组合资源死锁的必要条件

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


1.1 什么是死锁

哲学家进餐问题中,如果5位哲学家进程并发执行,都拿起了左手边的筷子……

semaphore chopstick[5]={ 1, 1, 1, 1, 1 };
Pi (){	W//i号哲学家的进程
    while(1){
    P(chopstick[i]);//拿左
    P(chopstick[(i+1)5]);//拿右
    吃饭...
    V(chopstick[i]);//放左
    V(chopstick[(i+1)5]);//放右
    思考...
    }
}

那么根据上面这段代码逻辑,五个哲学家同时阻塞,程序无法推进,这种由于进程竞争资源而引起的僵持称为”死锁“。

一组进程中的每个进程均等待此组进程中其他进程所占有的、因而永远无法得到的资源,这种现象称为进程死锁,简称死锁。

死锁 VS 饥饿 VS死循环

  • 死锁:各进程互相等待对方手里的资源,导致各进程都阻塞,无法向前推进的现象。
  • 饥饿:由于长期得不到想要的资源,某进程无法向前推进的现象。比如:在短进程优先(SPF)算法中,若有源源不断的短进程到来,则长进程将一直得不到处理机,从而发生长进程“饥饿”。
  • 死循环:某进程执行过程中一直跳不出某个循环的现象。有时是因为程序逻辑bug 导致的,有时是程序员故意设计的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.2 死锁的类型

1.2.1 竞争资源引起的死锁

这种类型的死锁是由于进程争夺使用系统中有限的资源而引起的,因而称为竞争资源引起的死锁,如定义所述。这种类型的死锁是本文接下来要讨论的核心。也就是说,在接下来的讨论中如未特别指明均假定死锁是由于进程竞争资源而引起的

1.2.2 进程间通信引起的死锁

假设在一个基于消息的系统中,进程P1等待进程P2发来的消息,进程P2等待进程P3发来的消息,进程P3等待进程P1发来的消息,如此3个进程均无法继续向前推进,亦即发生死锁这种类型的死锁在基于消息的系统中比较常见。

1.2.3 其他原因引起的死锁

例如,假设有一扇门,此门较小,一次只能通过一个人。又设有两位先生M1和M2,他们都要通过这扇门。显然,门相当于一个独占型资源。如果M和M,竞争地使用这一资源,则他们都能通过。但是假如他们都很谦虚,在门口处M1对M2说“您先过”,M2对M1说“您先过”,则二者均无法通过,造成僵持。这就是“After You / Afer You”问题。如果程序设计得不合理,也可能会发生类似的现象,在广义上也称为死锁。

1.3 死锁产生必要条件

假设死锁是由于进程竞争资源而引起的,下面给出死锁发生的4个必要条件。这4个必要条件是由Coffman于1971年首先提出的,所以也称为Coffman条件

  1. 资源独占。一个资源在同一时刻只能被分配给一个进程。如果某一进程申请某一资源,而该资源正在被另外的某一进程所占有,则申请者需要等待,直到占有者释放该资源。
  2. 不可剥夺。资源申请者不能强行地从资源占有者手中夺取资源,即资源只能由其占有者在使用完后自愿地释放。
  3. 保持申请。进程在占有部分资源后还可以申请新的资源,而且在申请新资源的时候并不释放它已经占有的资源。
  4. 循环等待。存在一个进程等待序列{ p1,p2,……,pn},其中p1等待p2所占有的某一资源,p2等待p3所占有的某一资源……pn等待p1所占有的某一资源。

**当且仅当上述4个条件同时满足时,死锁才会发生。**换言之,只要破坏上述4个条件中的任意一个,死锁就不会发生。

1.4 死锁的处理策略

  1. 不允许死锁发生
    1. 预防死锁(静态)。破坏死锁产生的四个必要条件中的一个或几个。是指对于进程有关资源的活动按某种协议加以限制,如果所有进程都遵守此协议,即可保证不发生死锁;
    2. 避免死锁(动态)。是指对于进程有关资源的申请命令加以实时检测,拒绝不安全的资源请求命令,以保证死锁不会发生。(如银行家算法)
  2. 允许死锁发生
    1. 死锁的检测和解除。允许死锁的发生,不过操作系统会负责检测出死锁的发生,然后采取某种措施解除死锁。

1.5 死锁的预防

1.5.1 破坏资源独占条件

资源独占条件:只有对必须互斥使用的资源的争抢才会导致死锁。

如果把只能互斥使用的资源改造为允许共享使用,则系统不会进入死锁状态。比如:SPOOLing技术。操作系统可以采用 SPOOLing 技术把独占设备在逻辑上改造成共享设备。比如,用SPOOLing技术将打印机改造为共享设备……

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

该策略的缺点:并不是所有的资源都可以改造成可共享使用的资源。并且为了系统安全,很多地方还必须保护这种互斥性。因此,很多时候都无法破坏资源独占条件

1.5.2 破坏不可剥夺条件

**不可剥夺条件:**进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。

破坏不可剥夺条件:

  • 方案一:**当某个进程请求新的资源得不到满足时,它必须立即释放保持的所有资源,待以后需要时再重新申请。**也就是说,即使某些资源尚未使用完,也需要主动释放,从而破坏了不可剥夺条件。
  • 方案二:**当某个进程需要的资源被其他进程所占有的时候,可以由操作系统协助,将想要的资源强行剥夺。**这种方式一般需要考虑各进程的优先级(比如:剥夺调度方式,就是将处理机资源强行剥夺给优先级更高的进程使用)

该策略的缺点:

  1. 实现起来比较复杂
  2. 释放已获得的资源可能造成前一阶段工作的失效。因此这种方法一般只适用于易保存和恢复状态的资源,如CPU。
  3. 反复地申请和释放资源会增加系统开销,降低系统吞吐量。
  4. 若采用方案一,意味着只要暂时得不到某个资源,之前获得的那些资源就都需要放弃,以后再重新申请。如果一直发生这样的情况,就会导致进程饥饿

1.5.3 破坏保持申请条件

保持申请条件:进程在占有部分资源后还可以申请新的资源,而且在申请新资源的时候并不释放它已经占有的资源

可以采用静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不让它投入运行。一旦投入运行后,这些资源就一直归它所有,该进程就不会再请求别的任何资源

该策略实现起来简单,但也有明显的缺点:有些资源可能只需要用很短的时间,因此如果进程的整个运行期间都一直保持着所有资源,就会造成严重的资源浪费,资源利用率极低。另外,该策略也有可能导致某些进程饥饿(旱的旱死涝的涝死)。

1.5.4 破坏循环等待条件

循环等待条件:存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求。

可采用顺序资源分配法。首先给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源,同类资源(即编号相同的资源)一次申请完。

原理分析:一个进程只有已占有小编号的资源时,才有资格申请更大编号的资源。按此规则,已持有大编号资源的进程不可能逆向地回来申请小编号的资源,从而就不会产生循环等待的现象。

如果我们存在一个环,那么环上拥有最大编号的进程没有资格申请小编号资源,我们一定可以消环。

该策略的缺点:

  1. 不方便增加新的设备,因为可能需要重新分配所有的编号
  2. 进程实际使用资源的顺序可能和编号递增顺序不一致,会导致资源浪费
  3. 必须按规定次序申请资源,用户编程麻烦。

1.6 死锁的避免

1.6.1 什么是安全序列

你是一位成功的银行家,手里掌握着100个亿的资金……

有三个企业想找你贷款,分别是企业B、企业A、企业T,为描述方便,简称BAT

B表示:“大哥,我最多会跟你借70亿……“

A表示:“大哥,我最多会跟你借40亿……”

T表示:“大哥,我最多会跟你借50亿……”

然而……江湖中有个不成文的规矩:如果你借给企业的钱总数达不到企业提出的最大要求,那么不管你之前给企业借了多少钱,那些钱都拿不回来了……

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

刚开始,BAT三个企业分别从你这借走了20、10、30亿……

最大需求已借走最多还会借
B702050
A401030
T503020

此时,A还想进一步借20亿,你敢借吗?

假如给了A20亿:

最大需求已借走最多还会借
B702050
A401030-20=10
T503020

此时,我们发现如果以后借钱顺序按照T->B->A,那么我们可以依次从三者收回贷款,给T20亿,完事T还回来50亿,然后给B50亿,完事B还回来70亿,然后再满足A,最后100亿全回来了。

或者先给A10亿,A还钱后手里有50亿,再给T20亿,T还钱后就有80亿,最后再给B借……

像上面两种借钱序列:T->B>A 或者 A->T->B 我们是可以满足所有企业的贷款并且成功收回贷款的,这样的序列其实就是安全序列

1.6.2 安全序列、不安全状态、死锁的联系

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

所谓安全序列,就是指如果系统按照这种序列分配资源,则每个进程都能顺利完成。只要能找出一个安全序列,系统就处于安全状态。当然,安全序列可能有多个

如果分配了资源之后,系统中找不出任何一个安全序列,系统就进入不安全状态。这就意味着之后可能所有进程都无法顺利的执行下去。当然,如果有进程提前归还了一些资源,那系统也有可能重新回到安全状态,不过我们在分配资源之前总是要考虑到最坏的情况。

如果系统处于安全状态,就一定不会发生死锁。如果系统进入不安全状态,就可能发生死锁(处于不安全状态未必就是发生了死锁,但发生死锁时一定是在不安全状态)

因此可以在资源分配之前预先判断这次分配是否会导致系统进入不安全状态,以此决定是否答应资源分配请求。这也是“银行家算法”的核心思想。

1.6.3 银行家算法

银行家算法是荷兰学者 Dikstra 为银行系统设计的,以确保银行在发放现金贷款时,不会发生不能满足所有客户需要的情况。后来该算法被用在操作系统中,用于避免死锁

**核心思想:**在进程提出资源申请时,先预判此次分配是否会导致系统进入不安全状态。如果会进入不安全状态,就暂时不答应这次请求,让该进程先阻塞等待。

银行家算法可以分为——资源分配算法 & 安全性检查算法

数据声明:假设系统中有n个进程,申请m个资源

可用资源:int Available[m];

进程所需最大资源量:int Claim[n][m];

各进程已经分配的资源量:int allocation[n][m];

各进程仍需的资源量:int Need[n][m],显然初始时有Need = Claim

当前各进程资源申请:int Request[n][m]

1.6.3.1 安全性检查算法

算法目标——检测当前是否存在安全序列

算法流程

  • Finish[i] 代表 进程i是否可被满足, res[m]代表各资源可用量
  • 不断寻找进程i,满足Finish[i] = false, Need[i] <= res
    • 找到,更新res[] = res[] + allocation[i], Finish[i]= true
    • 否则,跳出循环
  • 如果所有i,都满足Finish[i] = true,那么系统处于安全状态
  • 否则,系统处于不安全状态

算法时间复杂度O(n^2 m)

1.6.3.2 资源分配算法

当进程i发出资源请求Request[i]

算法流程

  1. Request[i] > Need[i], 申请量大于了声明最大需求量,返回false

  2. Available = Available - Request[i], Allocation[i] = Allocation[i] + Request[i], Need[i] = Need[i] - Request[i]

  3. 执行安全性检查算法

    1. 安全,分配资源,进程i继续执行

    2. 不安全,将进程i阻塞

      Available = Available + Request[i], Allocation[i] = Allocation[i] - Request[i], Need[i] = Need[i] + Request[i]

例: 设资源集合 R = { A, B, C }, 资源类 A 中含有 11 个 实例, 资源类 B 中含有 5 个实例, 资源类 C 中含有 8 个实例. 进程集合 P = { p0, p1, …, p4}. 某时刻系统状态如下:

ClaimAllocationNeedAvailable
A B CA B CA B CA B C
p08 5 31 1 27 4 13 2 3
p13 2 22 0 01 2 2
p29 0 33 0 06 0 3
p32 3 22 1 10 2 1
p44 4 30 1 24 3 1

我们此时尝试寻找安全序列, 找到: { p1, p3, p4, p2, p0 }, 因而断言系统此时处于安全状态.

现进程 p2 发出新的资源申请, Request[2] = (1, 0, 2), 请你判断该资源是否可以分配.

解:

首先有Request[2] < Need[2]

然后假定分配资源后状态变为

ClaimAllocationNeedAvailable
A B CA B CA B CA B C
p08 5 31 1 27 4 12 2 1
p13 2 22 0 01 2 2
p29 0 33 0 05 0 1
p32 3 22 1 10 2 1
p44 4 30 1 24 3 1

运行安全性检测算法, 找到安全序列: <p3, p1, p4, p0, p2>, 因而上述状态是安全的.

所以我们可以为p2分配资源.

1.7 死锁的检测与消除

1.7.1 资源分配图

进程的死锁问题可以用有向图更加准确地加以描述,这种有向图称为系统资源分配图(resource allocation graph),经过对资源分配图的约简将得到重要的死锁定理。

资源分配图的定义

一个系统资源分配图是一个二元组 G = (V,E),其中V是结点集,E是边集。结点集定义为V = P U R,

其中

  • p = { p1,p2,…,pn }为系统中所有进程所构成的集合
  • R={ r1,r2,…,rn }为系统中所有资源类所构成的集合。
  • 边集E = { (pi,ri) } U { (rj,pj) },其中pi ∈ P,rj ∈ R
  • 如果(pi,rj) ∈ E,则有一条由进程pi 到资源类rj的有向弧,表示进程pi申请资源类rj中的一个资源实例。
  • 如果(rj,pi) ∈ E,则有一条由资源类rj到进程pi的有向弧,表示资源类rj中的一个资源实例被进程pi占有。
  • 将形如(pi,rj)的边称为申请边,将形如(rj,pi)的边称为分配边。

根据上述关于资源分配图的定义,容易证明:

如果图中没有环路,则系统中没有死锁; 如果图中存在环路,则系统中可能存在死锁。

存在环路不一定有死锁是因为一个资源类内的资源实例可以有多个。比如下面的例子

存在环路: p1 -> r2 -> p3 -> r1 -> p1

但是p2是有可能释放一个r1的资源实例的, 所以当p2释放资源后, p3 可以 获得一个r1 资源实例, 此时环路解除, 死锁消除.

可见环路是死锁存在的必要条件而非充分条件。

例:进程集P、资源类集R、边集E:

P = { p1, P2, P3 }
R = { r1(2), r2(3), r3(1), r4(3) }
E = { (r1, p1), (r1, p2), (r2, p1), (r2, p1), (r2, p2), (r3, p3), (r4,p3), (p1, r1), (p2, r3) }

其中资源类后括号内的数字均代表资源实例的个数

我们尝试画出上述描述的资源分配图:

黑色边是由已分配资源实例指向分配进程

橙色边是由资源申请进程指向资源实例

显然无环,无死锁发生。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.7.2 资源分配图的约简

通过资源分配图的约简来检测系统是否处于死锁状态. 资源分配图的约简方式如下:

  1. 寻找非孤立且无请求边的进程节点pi, 如果不存在这样的结点, 算法结束
  2. 否则, 去除pi的所有分配边, 回收资源
  3. 寻找所有请求边都能被满足的进程pj, 将其请求边全部转化为分配边
  4. 转到1

算法结束时, 如果所有结点均为孤立结点, 那么称资源分配图是可以完全约简的. 否则是不可完全约简的.

死锁定理

S 为死锁状态的充分必要条件是 S 的资源分配图不可完全约简.

1.7.3 死锁检测算法

死锁检测算法由 Shoshani 和 Coffman 所提出, 所用数据结构如下:

  1. Available: 长度为 m 的向量, 记录当前各个资源类中空闲资源实例的数目.
  2. Allocation: m × n 的矩阵, 记录当前每个进程占有各个资源类中资源实例的个数.
  3. Request: m × n 的矩阵, 记录当前每个进程申请各个资源类中资源实例的个数.

为了简洁表达, 我们把 矩阵Allocation 和 Request的行看作向量, 分别表示为 Allocation[i] 和 Request[i].

算法流程:

  1. 令 Work 和 Finish 分别是长度为 m 和 n 的向量, 分别表示可用资源和进程完成是否完成, 初始化:

    • Work = Available
    • 对于所有的 i = 1, 2, …, n, 如果 Allocation[i] != 0, 则 Finish[i] = False, 否则 Finish[i] = true.
  2. 寻找满足下述条件的下标 i:

    • Finish[i] = false
    • Request[i] <= Work
    • 如果不存在上述 i , 则转到步骤4
  3. Work = Work + Allocation[i]

    Finish[i] = True

  4. 如果存在 i , 1<= i <= n, Finish[i] = False, 则系统处于死锁状态. 且进程 pi 参与了死锁

不难发现, 死锁检测算法和我们上面的资源分配图约简过程一致.

时间复杂度 O ( n 2 m ) O(n^2m) O(n2m)

定理: P是进程集合, P’ 是 所有占有资源的进程集合, 则 P 中存在死锁和 P’ 中存在死锁等价

证明很简单, 这里略.

1.7.4 死锁检测时刻

死锁检测时刻取决于:

  1. 死锁发生频率
  2. 死锁涉及的进程数目

我们一般可以在如下时刻进行死锁检测:

  1. 进程等待时刻检测
    • 每当进程申请资源不能被满足发生等待时我们就进行检测, 显然代价很高
  2. 定时检测
  3. 资源利用率低时检测
    • 因为死锁会使得更多进程等待, 导致CPU利用率低, 因此我们设个界限如45%, 此时进行死锁检测.

1.7.5 死锁的恢复

  1. 系统重启动法
    • 最现实、最简单也是代价最高的一种
  2. 终止进程法
    • 终止那些参与死锁的进程, 并回收其资源.两种策略:
      • 一次撤销所有参与死锁的进程. 但是代价较高, 有些不占有资源的进程也会被撤销
      • 逐一撤销参与死锁的进程, 如果撤销一个进程后仍然存在死锁, 就选择下一个进程进行撤销. 对于撤销进程的选择可以自己定优先级
  3. 剥夺资源法
    • 剥夺死锁进程占用的全部或者部分资源. 也分两种实现:
      • 逐步剥夺, 一次剥夺死锁进程的一个或一组资源, 如果死锁尚未解除就再继续剥夺, 直到死锁解除
      • 一次剥夺
  4. 进程回退
    • 类似于我们用虚拟机时经常在VMWare中打快照, 我们不断的按照时间顺序回退到之前的快照, 直到没有死锁

1.8 鸵鸟算法

鸵鸟遇到危险时将头埋进沙子.

鸵鸟算法(ostrich algorithm)选择对死锁视而不见. 这实际上不能算一个算法, 但却是目前操作系统中采用最多的策略, 比如UNIX和Windows. 那么出现死锁怎么办呢? —— 重启吧x_x

实际上这个买卖是很划算的, 死锁检测开销太大, 而死锁事实上发生的频率很低, 偶尔发生一次还是可以接受的.

1.9 饥饿与活锁

1.9.1 饥饿与饿死

饥饿(starvation): 当等待时间给进程的推进和响应带来明显的影响时,就称发生进程饥饿。当饥饿到一定程度的进程所赋予的任务即使完成也不再具有实际意义时, 称该进程被饿死(starve to death)

饿死和死锁都是由于竞争资源而引起的, 但是又有明显差别:

  1. 从进程状态考虑: 死锁进程都处于等待态. 忙式等待(处于运行态或就绪态)的进程并非处于等待态, 但是却可能被饿死.(如前面章节进程互斥算法中忙等的进程)
  2. 死锁进程等待永远不会被释放的资源, 饿死进程等待会被释放但却不会分配给自己的资源. 其等待时限没有上界(排队等待或忙式等待)
  3. 死锁一定是发生了循环等待, 而饿死则不一定. 这也说明资源分配图可以检测死锁却不能检测进程饿死.
  4. 死锁一定涉及多个进程, 而饥饿或被饿死的进程可能只有一个.

1.9.2 活锁

当进程申请资源不能满足时,便进入等待状态,待资源得到时被唤醒,这种等待称为排队等待。与忙式等待相比,排队等待的进程不会分派CPU,因而减少了对处理器不必要的占用时间.

当资源竞争不是很激烈的情况下, 进程等待时间往往很短, 此时如果等待时间和进程切换开销相差不大, 我们不如让其忙等, 而非排队等待.

忙式等待条件下发生的饥饿称为活锁(live lock)

例如: UNIX下, 我们假设 进程表proc 共有50个表项, 10个进程每个进程都要创建5个子进程, 每个进程执行如下代码段

如果创建失败, 就会睡眠一段时间然后再次创建

for (int i = 1; i <= 5; i ++ ) 
    while (fork() == -1)	//创建失败
        sleep(5);

那么当每个进程都创建4个子进程时, proc表用完, 发生活锁. 与死锁不同, 活锁可能会解除, 如上例, 只要有进程运行结束, proc有表项空闲, 忙等的进程就可能结束忙等.

1.10 死锁的例子

1.10.1 过河问题1

有南北走向的河流如下图所示,河中有用石块搭成的便桥,每个石块上最多容纳一个过河者,两个相邻石块的间距恰好为一步。西岸过河者经过石块1、2、…、n 到达东岸, 东岸过河者经过石块n、n - 1、…、1到达西岸。试分析可能发生的死锁情况,给出一个无死锁、无饿死、并行度高的解法,并用信号量和PV操作实现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

int west_crossing, east_crossing;
int west_wait, east_wait;	//两边等的人
semaphore wq, eq;	//两边是否能过河
semaphore mutex;	//互斥锁
void WestMan() {
    P(mutex);
    if (east_crossing > 0) {
        west_wait = west_wait + 1;
        V(mutex);
        P(wq);
    }
    else {
        west_crossing = west_crossing + 1;
        V(mutex);
    }
    过河
    P(mutex);
    west_crossing = west_crossing - 1;
    if (west_crossing == 0) {
        east_wait = east_wait - 1;
        east_crossing = east_crossing + 1;
        V(eq);
    }
    V(mutex);
}

void EastMan() {
    P(mutex);
    if (west_crossing > 0) {
        east_wait = east_wait + 1;
        V(mutex);
        P(eq);
    }
    else {
        east_crossing = east_crossing + 1;
        V(mutex);
    }
    过河
    P(mutex);
    east_crossing = east_crossing - 1;
    if (east_crossing == 0) {
        west_wait = west_wait - 1;
        west_crossing = west_crossing + 1;
        V(wq);
    }
    V(mutex);
}

上述解法其实问题很明显,如果某方向源源不断的有人到来, 那么另一边的人就会饿死。

那么如何设计无饥饿解法?

考虑读者写者问题中的读写公平解法,增加一个信号量代表过河权,每个人只有拿到这个信号量才能过河,过完河再释放。这样就能够保证两边都不会有人饿死了。

代码略。

1.10.2 过河问题2

有南北走向的河流如下图所示,河中有用石块搭成的便桥,每个石块上最多容纳一个过河者,两个相邻石块的间距恰好为一步。西岸过河者经过石块1、2、5、6、4、3 到达东岸,东岸过河者经过石块3、4、7、8、2、1到达西岸。试分析可能发生的死锁情况,给出一个无死锁、无饿死、并行度高的解法,并用信号量和PV操作实现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

分析最大并行度:8个格子都占满:如左边两人在5、6上,右边的人都占满了,此时能够推进下去

避免死锁:对于一遍正在过河的人数大于等于3,另一边过河人数不能超过2

防止被对方堵住:西边的人先申请3再申请4,东边的人先申请1,再申请2

显然不会饿死

semaphore mutex;	//互斥锁
int ec, wc;	//两侧过河人数
int ew, ww;	//两侧等待人数
semaphore S1, S2, S3, S4, S5, S6, S7, S8;	//8块石头
void WestMan() {
    P(mutex);
    if (ec >= 3 && wc == 2) {
        ww ++;
        V(mutex);
        P(wq);
    }
    else {
        wc ++;
        V(mutex);
    }
    P(S1);
    P(S2);
    V(S1);
    P(S5);
    V(S2);
    P(S6);
    V(S5);
    P(S3);
    P(S4);	//先3再4
    V(S6);
    V(S4);
    V(S3);
    P(mutex);
    wc --;
    if (wc == 2)
        while (ew > 0) {
            ew --;
            ec ++;
            V(eq);
        }
    else if (wc == 1 && ww > 0){
        ww --;
        wc ++;
        V(wq);
    }
    V(mutex);
}
void EastMan() {
    P(mutex);
    if (wc >= 3 && ec == 2) {
        ew ++;
        V(mutex);
        P(eq);
    }
    else {
        ec ++;
        V(mutex);
    }
    P(S3);
    P(S4);
    V(S3);
    P(S7);
    V(S4);
    P(S8);
    V(S7);
    P(S1);
    P(S2);	//先3再4
    V(S8);
    V(S2);
    V(S1);
    P(mutex);
    ec --;
    if (ec == 2)
        while (ww > 0) {
            ww --;
            wc ++;
            V(wq);
        }
    else if (ec == 1 && ew > 0){
        ew --;
        ec ++;
        V(wq);
    }
    V(mutex);
}

1.11 可复用资源的静态分析

1.11.1 相关概念

**可复用资源:**一次只能分配给一个进程使用的资源,其使用后仍存在,且可再被分配给其他进程使用。

如:打印机、磁带都是可复用资源。

由相对独立的若干资源构成的资源集合称为组合资源,其中每个相对独立的资源称为子资源

由相同类型的子资源构成的组合资源,称为同种组合资源

1.11.2 可复用资源死锁的静态分析算法

  1. 以每个进程占有资源又申请资源作为一个状态,记作 ( p i : a j : a k 1 , a k 2 , … , a k n ) (p_{i}:a_{j}:a_{k_{1}},a_{k_{2}},\dots ,a_{k_{n}}) (pi:aj:ak1,ak2,,akn)=(进程:当前请求:已占有资源)

  2. 以每个状态作为一个结点

  3. 如果 s 1 s_{1} s1所申请的资源为 s 2 s_{2} s2所占有,则由 s 1 s_{1} s1 s 2 s_{2} s2画一条有向弧

  4. 找出所有环路

  5. 判断环路上的状态是否能同时到达。如是则有死锁的可能性,否则无死锁的可能性。

    事实上,如果环路上出现了相同的进程,说明该环路的状态不能同时到达(因为一个进程不可能同时处于多个状态)

    类似地,如果环路上出现了相同的已占有资源,也说明环路状态不能同时到达。

    不难发现,如果上面两种情况都没出现的话,我们就要进一步分析该环路的状态是否可以同时到达了。

例:设有可复用资源R = { A, B, C, D, E, F, G },进程P1, P2, P3 有关资源的活动如下:

P 1 : c   d   c ˉ   a   b   d ˉ   a ˉ   b ˉ P_{1}: c\ d\ \bar c\ a\ b\ \bar d\ \bar a\ \bar b P1:c d cˉ a b dˉ aˉ bˉ

P 2 : d   e   d ˉ   b   f   e ˉ   b ˉ   f ˉ P_{2}: d\ e\ \bar d\ b\ f\ \bar e\ \bar b\ \bar f P2:d e dˉ b f eˉ bˉ fˉ

P 3 : c   e   e ˉ   f   a   c ˉ   f ˉ   a ˉ P_{3}: c\ e\ \bar e\ f\ a\ \bar c\ \bar f\ \bar a P3:c e eˉ f a cˉ fˉ aˉ

(带-代表释放资源,否则代表申请资源)

那么一共有9个状态;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

画有向弧:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

找到环路:(P1: b: ad) -> (P2: f: eb) -> (P3: a: cf)

环路上既没有相同进程也没有相同的占有资源,所以我们需要进一步分析:

环路上状态对应三个进程推进到下面的线①

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们假设①可以到达,那么④也可以到达,因为a、b、f这三个资源三个进程都没有,从④跨到①谁先来无所谓

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

那么我们来看④是否可以同时到达

由于P3需要c,而P1要释放c,所以P1比P3早到达,P1 < P3

P2需要e,P3释放e,所以P3比P2早到达,P3 < P2

因为P2释放d,P1需要d,所以P2 < P1

P1 < P3 < P2 < P1,矛盾,故④不可同时到达,进而得出①不可同时到达

1.12 同种组合资源死锁的必要条件

设某类资源共有M个实例,使用这种资源的进程共有N个,这N个进程中的每个进程至少需要一个资源实例。如果一个资源实例也不需要,就不算在N个进程之中。这N个进程并发执行时发生死锁的必要条件如下:
所有进程所需资源总量 ∑ ≥ M + N 所有进程所需资源总量\sum \ge M + N 所有进程所需资源总量M+N
证明如下:
$$
\begin{align}
&假定已经发生死锁,且n个进程(2\le n \lt N)被卷入死锁。由于死锁已经发生,系统M个资源均已被n个死锁进程所占有,\ \
& 而且还不够,每个死锁进程至少还需要一个资源实例,n个进程至少还需要n个资源实例。有 \ \

& 死锁进程所需要的资源总量 \ge M + n \ \

& 每个未死锁(已经执行完毕)进程至少需要一个资源实例,N - n个进程至少需要N - n个资源实例,则 \ \

& 未死锁进程所需要的资源总量 \ge N-n \ \

& 则 所有进程所需资源总量\sum \ge M + n + N - n = M + N \ \
\end{align}
$$

例: 某类资源共有16个实例,进程P需要5个实例,进程P需要6个实例,进程P需要4个实例,进程P需要4个实例。请你判断是否会产生死锁。

5 + 6 + 4 + 4 < 16 + 4 = 20,故没有死锁

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

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

相关文章

R可视化:另类的箱线图

介绍 方格状态的箱线图 加载R包 knitr::opts_chunk$set(echo TRUE, message FALSE, warning FALSE) library(patternplot) library(png) library(ggplot2) library(gridExtra)rm(list ls()) options(stringsAsFactors F)导入数据 data <- read.csv(system.file(&qu…

记一次Chanakya靶机的渗透测试

Chanakya靶机渗透测试 首先通过主机发现发现到靶机的IP地址为:172.16.10.141 然后使用nmap工具对其进行扫描:nmap -sC -sV -sS -p- 172.16.10.141 发现目标靶机开启了80,22,21等多个端口&#xff0c; 访问80端口,发现是一个普通的页面,点击进入多个界面也没有其他有用的信息,然…

PaliGemma – 谷歌的最新开源视觉语言模型(一)

引言 PaliGemma 是谷歌推出的一款全新视觉语言模型。该模型能够处理图像和文本输入并生成文本输出。谷歌团队发布了三种类型的模型&#xff1a;预训练&#xff08;PT&#xff09;模型、混合&#xff08;Mix&#xff09;模型和微调&#xff08;FT&#xff09;模型&#xff0c;每…

【二叉树】非递归实现前中后序遍历

目录 前言 算法思想 非递归实现前序遍历 过程分析 代码 非递归实现中序遍历 过程分析 代码 非递归实现后序遍历 过程分析 代码 前言 1&#xff09;前序&#xff1a;根 左子树 右子树 2&#xff09;中序&#xff1a;左子树 根 右子树 3&#xff09;后序&#xff1…

Mysql 8.0 主从复制及读写分离搭建记录

前言 搭建参考&#xff1a;搭建Mysql主从复制 为什么要做主从复制&#xff1f; 做数据的热备&#xff0c;作为后备数据库&#xff0c;主数据库服务器故障后&#xff0c;可切换到从数据库继续工作&#xff0c;避免数据丢失。架构的扩展。业务量越来越大&#xff0c;I/O访问频…

PID控制中积分项目的理解,消除稳态误差的作用,表示着过去(PID积分控制)

1&#xff0c;消除稳态误差 积分项目是对于历史误差进行的累积&#xff0c;可以理解&#xff0c;系统的误差累积表示不断的在减少误差&#xff0c;最终消除误差&#xff0c;这个过程需要将误差进行累加&#xff0c;才可以真正知道误差的大小是多少&#xff0c;用最终累加的误差…

C++模板方法模式

文章目录 1. 定义抽象基类&#xff08;Abstract Class&#xff09;2. 实现具体子类&#xff08;Concrete Class&#xff09;3. 使用模板方法模板方法模式的优点模板方法模式的应用场景注意事项实现示例抽象类&#xff08;模板&#xff09;具体实现类客户端代码 总结 模板方法模…

期望薪资25K,新浪微博测试4轮面试,没想到过了。。

一面60min 1、离职原因 2、简单的算法题&#xff0c;就是我会什么让写什么&#xff1a; 冒泡排序&#xff0c;二分查找&#xff08;其实这么简单&#xff0c;我还是在指引下写出来的&#xff0c;自己实在太菜&#xff09; 3、简历问答&#xff08;随机抽几个点问&#xff0…

MySQL数据库语法(二)

一、数据库的创建 创建数据库CRATE DATABASE语法&#xff1a;CREATE DATABASE [IF NOT EXISTS]数据库名;功能&#xff1a;用给定的名字创建一个数据库如果数据库已经存在&#xff0c;发生一个错误。查看创建数据库&#xff1a;SHOW CREATE DATABASE <数据库名>&#xff…

Oracle数据库操作问题汇总

一、简介 Oracle Database&#xff0c;又名Oracle RDBMS&#xff0c;或简称Oracle。是甲骨文公司的一款关系数据库管理系统。它是在数据库领域一直处于领先地位的产品。可以说Oracle数据库系统是世界上流行的关系数据库管理系统&#xff0c;系统可移植性好、使用方便、功能强&…

【数据结构】快速排序(详解)

目录 快速排序 历史&#xff1a; 基本思想&#xff1a; 主框架&#xff1a; 下面解释实现单次排序的几种版本&#xff1a; 1.Hoare版本 2. 挖坑法 3. 前后指针法 快速排序的实现包括递归与非递归&#xff1a; 1. 递归实现&#xff1a;&#xff08;即开头的基本框架&am…

工程机械比例阀电流采集方案——IPEhub2与IPEmotion APP

自从国家实施一带一路和新基建计划以来&#xff0c;工程机械的需求量呈现出快速增长的趋势。而关于工程机械&#xff0c;其比例阀的控制问题不容忽视。比例阀是一种新型的液压控制装置——在普通压力阀、流量阀和方向阀上&#xff0c;用比例电磁铁替代原有的控制部分&#xff0…

KEIL5鼠标右键查找定义或声明选项变灰色不可选

原因&#xff1a;我直接点的KEIL图标打开了昨天的工程 解决办法&#xff1a;关掉工程&#xff0c;重新从文件夹的路径打开 其他原因导致试试以下方法&#xff1a; 1.快捷键F12导航到目标位置 2.路径不能含有中文&#xff0c;改好后&#xff0c;shiftAltf12&#xff0c;更新搜索…

疑惑点:动作监听时this的含义:可以理解为接口的多态

全部代码&#xff1a; package test;import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Random;public class test3 extends JFrame implements ActionListener {JButton jb1 new JButton("你点我啊&am…

纽曼硬盘隐藏文件丢失怎么恢复?介绍几种有效的方法

纽曼硬盘作为存储设备中的佼佼者&#xff0c;以其高性能和稳定性受到了广大用户的青睐。然而&#xff0c;在使用过程中&#xff0c;有时我们可能会遇到一些意想不到的问题&#xff0c;比如隐藏文件的丢失。这对于依赖这些文件进行工作或生活的人来说无疑是一个巨大的困扰。那么…

香橙派AI Pro测评--ROS及激光SLAM

文章目录 前言一、外形与质感二、软件测评1. 系统界面2. ROS安装3. ROS节点测试4. SLAM算法测试 总结 前言 今天刚收到了官方寄来的“香橙派 AI Pro”开发板&#xff0c;这篇文章将会对香橙派 AI Pro的外形、质感、运行速度进行一个测评&#xff0c;然后我会在这个开发板上安装…

四步简单操作:轻松将iCloud照片恢复到相册

随着智能手机的普及&#xff0c;我们的生活中越来越多的照片存储在了云端&#xff0c;其中iCloud提供了便捷的照片备份和存储服务。但有时候&#xff0c;我们可能会不小心删除了在iCloud上的照片&#xff0c;或者想要将iCloud中的照片恢复到手机相册中。 在这篇文章中&#xf…

I.MX6ULL Linux 点灯实验理论及汇编点灯

系列文章目录 I.MX6ULL Linux C语言开发 I.MX6ULL Linux 点灯实验理论 系列文章目录一、I.MX6ULL GPIO二、I.MX6ULL IO 命名三、I.MX6ULL IO 复用四、I.MX6ULL IO 配置五、I.MX6ULL GPIO 配置六、I.MX6ULL GPIO 时钟使能七、硬件原理分析八、实验程序编写 一、I.MX6ULL GPIO 一…

flutter 的webview中touchstart和touchend 执行异常问题解决

效果 背景 使用flutter 调用webview内网页&#xff0c;网页内容是监听touchstart和 touchend&#xff0c;触发不同是事件&#xff0c;但是发现每次长按都 手指抬起后 才会执行 touchstart和touchend&#xff0c;满足不了我的需求&#xff0c;我的需求是当手指按下 立即执行touc…