【OS】第九部分:同步互斥

【OS】第九部分:同步互斥
视频:B站清华大学 向勇、陈渝老师
链接: https://www.bilibili.com/video/BV1js411b7vg?p=11

9.1背景知识

1、如果资源处理不当,可能会出现一些意想不到的情况,合作的风险
独立的线程:

不和其他线程共享资源或状态
确定性->输入状态决定结果
可重现->能够重现起始条件
调度顺序不重要
合作线程:

在多喝线程中共享状态
不确定性
不可重现(不可重复性)
这些不确定性和不可重复以意味着bug可能是间歇性发生的,也就是合作是有风险的。

2、为什么要合作
1)共享资源
资源是需要共享的,因为进程可能要访问同一个文件。
2)加速
通过并行和并发,可以提高系统的效率,实现更有效的资源的利用。相当于把一个大的任务,拆分成多个小的任务,每个任务通过并行的执行提高系统的性能。
3)模块化
在设计时将一个大的工作,变成一个小的工作,使之具有模块化,使系统便于扩展。

3、问题出现的原因
eg
在这里插入图片描述
以上四条汇编指令的意思是:
1)把next_pid赋值给寄存器1(Reg1)
2)再把这个寄存器1存到了new_pid这个内存单元的去。此时new_pid就具有了next_pid这个值。
3)寄存器1加一操作。
4)完成next_pid的值增加了一个1的操作。
总的实现过程:
先把new_pid = next_pid,然后next_pid再加1.
但是,如果这时有两个进程,就会出现意想不到的情况:
在这里插入图片描述
问题产生的原因:
在第二次进程的上下文切换时候,进程1的寄存器恢复之后依然100的值,使得next的值无法更新成为102。最终产生了切换使得最终的结果不是想要的结果。这是一种典型的异常现象。

9.2一些概念part1

由于上述产生的异常现象(称之为竞态条件Race Condition),这就是为什么要引入同步互斥这些机制的原因,就是要解决这种不确定性的问题。

1、系统缺陷:结果依赖于并发执行或者事件的顺序/时间
不确定性
不可重现

2、怎样避免竞态?
让指令不被打断(比如上述的四条机器指令不被打断)

3、不被打断的方法:原子操作(Atomic Operation)—不可被打断操作
原子操作是指一次不存在任何中断或者失败的执行

该执行成功结束
或者根本没有执行
并且不应该发现任何部分执行的状态
实际上操作往往不是原子的

有些看上去是原子操作,实际不是
连x++这样简单的语句,实际上是由3条指令造成的
有时候甚至连条单条机器指令都不是原子的
例子:
在这里插入图片描述
不保证会有人赢 可能A赢可能B能 也可能一直陷在循环里
所以需要后续的同步机制,确保或者是A赢或者是B赢。
4、一些基本概念

临界区(Critical section)
临界区是指进程中的一段需要访问共享资源并且当另一个进程处于相应代码区域时便不会被执行的代码区域。简单来说,就是访问共享资源的那段代码就是临界区。

互斥(Mutual exclusion)
当一个进程处于临界区并访问共享资源时,没有其他进程会处于临界区并且访问任何相同的共享资源。

死锁(Dead lock)
两个或以上的进程,在互相等待完成特定任务,而最终没法将自身任务进行下去。

饥饿(Starvation)
一个可执行的进程,被调度器持续忽略,以至于虽然处于可执行状态却不被执行

备注:
临界区是指一段代码要访问的资源是共享资源 这段代码就是临界区
临界区指的是一个访问共用资源(例如:共用设备或是共用存储器)的程序片段,而这些共用资源又无法同时被多个线程访问的特性。当有线程进入临界区段时,其他线程或是进程必须等待(例如:bounded waiting 等待法),有一些同步的机制必须在临界区段的进入点与离开点实现,以确保这些共用资源是被互斥获得使用,例如:semaphore。只能被单一线程访问的设备,例如:打印机。
每个进程中访问临界资源的那段代码称为临界区(Critical Section)(临界资源是一次仅允许一个进程使用的共享资源)。每次只准许一个进程进入临界区,进入后不允许其他进程进入。不论是硬件临界资源,还是软件临界资源,多个进程必须互斥地对它进行访问。
多个进程中涉及到同一个临界资源的临界区称为相关临界区。
.

9.3一些概念part2

1、一个有趣的类比:在这里插入图片描述
2、解决的方法和概念
在这里插入图片描述
3、更好的解决方法(轻量级)
在这里插入图片描述
但是由于进程上下文切换的原因,问题还是会存在。在这里插入图片描述
如果只是将Note往前面提简单的挪动一下还是不会解决问题,变成谁都不会去买面包了。
在这里插入图片描述

9.4一些概念part3

1、再换一个方法
在这里插入图片描述
结果还是有问题的。
在这里插入图片描述
需要确保在任何情况下,只有一个进程在临界区中执行,其他的进程需要在外面等待。

一个更加合理的方案解析过程:
在这里插入图片描述
在这里插入图片描述
这个设计不对称 A去买面包的可能性更大
在这里插入图片描述
只能有一个进程在临界区中执行,如果已经有一个进程在临界区中执行了,其他的进程只能在临界区之外等待
在这里插入图片描述
最终的解决方案:
为每一个线程保护了一段“临界区”代码。使用临界区的思想,问题就可以较好的解决。其是讲前诉方法的一个抽象。
有了临界区的代码之后,就可以确保任何时候只有一个进程在临界区中执行,切其他进程在外面等待,知道临界区中的进程离开,其他进程中的一个会进入临界区去执行。这个是比较合理的一个实现。

9.5临界区

在临界区中执行所拥有的属性:
1、互斥:同一个时间临界区中最多存在一个线程
2、前进(Progress):如果一个线程想要进入临界区,那么它最终会成功,不会一直的死等。
3、有限等待:如果一个线程i处于入口区,那么在i的请求被接受之前,其他线程进入临界区的时间是有限制的。如果是无限等待,就会出现饥饿状态。是Progress前进状态的一种进一步补充。
4、无忙等待(可选属性):如果一个进程在等待进入临界区,那么在它可以进入之前会被挂起。

基于这些属性,设计一些方法对临界区进行保护:
方法一:禁用硬件中断
方法二:基于软件的解决方法
方法三:更高级的抽象(基于硬件原子操作的指令)

9.6禁用硬件中断

一、基本实现
没有中断,也就是没有了上下文切换,因此没有并发。

硬件将中断处理延迟到中断被启用之后
大多数现代计算机体系结构都提供指令来完成

进入临界区时禁用中断,离开临界区时开启中断。这个方法是可以解决问题的。

二、缺点
1、一旦中断被禁用,线程就无法被停止
2)整个系统都会为你停下来
3)可能导致其他线程处于饥饿状态(临界区可能很长)

2、要是临界区可以任意长
无法限制响应中断所需的时间(可能存在硬件影响)

需要注意:
执行这种课屏蔽中断的指令,只是把自身的响应中断的能力屏蔽了,并不意味着也将其他cpu的响应中断能力屏蔽,所以其实其他的cpu还是可以继续产生中断,所以在多cpu的情况下是无法解决互斥问题的。

9.7基于软件的解决方案

一、思考方案
1、思考方案一:

某一个进程,它想进入临界区,其有一个顺序(次序),根据这个次序决定谁会进入这个临界区。

方法如下所示:
在这里插入图片描述
假设这个线程的次序是0,那么当turn=0时,才去继续下面的执行临界区代码,否者在while循环中一直打转。条件满足时,改变使得turn=1。
而进程1的代码也是类似的,只是while循环中的判断条件是不等于0,下面的turn=0.

这个程序的弊端是:
必须进程1执行一次临界区,进程0执行一次临界区,然后两个交替执行,才能保证两继续的执行。一旦其中的一个进程不愿意再做这个事情,那按照之前的属性,其他进程先进去就应该能够进去,但是在这种模式下,就无法完成这个前进的属性。

2、思考方案二:
前面表示了一个turn是不够表示,所以接下来使用一个小数组flag[2]来表示这个进程是否想进入临界区。

flag[0] = 1	//表示进程0想进入临界区执行
flag[1] = 1	//表示进程1想进入临界区执行

方法1如下所示:
在这里插入图片描述但是这个代码是有问题的,不能满足互斥这个属性。
因为在初始的时候,两个进程都不会想进入临界区,所以两个flag都会赋值为0,表面没有这个需求。这样就使得两个进程都会跳出while这个循环,然后都会将flag值赋值为1,想要进入临界区,也就出现了多买面包的想象。
方法2如下所示:
在这里插入图片描述
满足了互斥,但是倘若两个线程都赋值了1,出现上下文切换的时候,都无法跳出这个循环,也就是出现是死锁的情况。
可见,用软件解决互斥并没有想象的那么简单
二、正确实现
1、正确的接法

将以上的两种思考都综合起来使用。三个变量共同的作用。
在这里插入图片描述
在这里插入图片描述
该算法可以满足互斥,前进和有限等待三个属性。
反证法来证明:
假定,现在两个进程都进入了临界区,都在执行临界区代码,但是turn只是一个值的,所以总会有一个线程会跳出循环的。
2、另外一种算法
所需的变量空间相同,但是更加的复杂在这里插入图片描述
三、拓展
1、n进程解决方法1 (E&M算法)

除了了针对两个进程之外,还可以拓展到n个进程如何保证互斥。
在这里插入图片描述
大致的思路:
对于进程i而言,对于其前面的进程,如果有进程想进入临界区,或者已经进入了临界区,那么i进程就必须等待。而对于i进程后面的进程,必须要等待i进程执行之后再进入临界区。这样就可以通过一个循环的方式完成n个进程有序的进入临界区。
2、n进程解决方法2(Bakery算法)
大致思路如下:
在这里插入图片描述
在这里插入图片描述
四、总结
1)即使是针对两个进程的解决竞态的实现还是比较复杂的。
2)需要忙等待,浪费cpu时间。
3)没有硬件包装的情况下无真正的软件解决方案。对硬件的需求比较低(只需要load操作和store是原子操作即可)

9.8更高级的抽象 — 基于原子操作

软件的处理比较复杂有没有更加简单的实现方法?
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
使用了一些特殊的操作:
1、Test-and-Set测试和置位
(一条机器指令,但是完成了读写操作两条指令的功能)
从内存中读取值
测试该值是否为1(然后返回真或假)
内存值设置为1

2、交换
(交换内存中的两个值)

只要计算机系统提供了这两条的其中一条指令就可以很容易的完成互斥的问题。
二、解决的方法
1、Test-and-Set方式

在这里插入图片描述
解决忙等的情况:先让其睡眠,再加一步唤醒操作
在这里插入图片描述
两者的区别:

忙等:不需要上下文切换,但是利用率低,适用与临界区执行时间短的情况。
不忙等:需要上下文切换,上下文切换开销比较大大,适用于临界区很长,远远大于上下文切换所需要的开销。

2、交换的方式
在这里插入图片描述
解析:
1)当一个进程想要进入临界区的时候,key=1,而且lock的初始值是0,所以当执行到while循环的时候,由于执行了交换,交换执行的过程不会被打断进行上下文切换的操作,而后lock的变成了1,而key变成了0.所以会退出循环,执行临界区的代码。
2)需要注意的是,当进入临界区的时候,load已经是1,当其他进程进入临界区执行的时候,load是1,而key也是1,交换之后还是1,一直会循环的等待,进入不了临界区。知道进入临界区的进程,退出临界区之后,完成一个将load变成0的操作。其他等待的进程才会继续执行exchange。
在这里插入图片描述
在这里插入图片描述
高优先级的等待 低优先级的就无法释放
需要用优先级反转的方式进行处理
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值