线程的同步与互斥
- 同步:
按照特定顺序访问临界资源,在互斥基础上实现。 - 互斥:对临界资源的访问时串行式的。每次只能有一个线程访问。
对于多个线程的访问,访问冲突的问题是很普遍的,解决的办法是引入互斥锁,获得锁的线程可以完成“读-修改-写”三步操作组成原子操作,要么都执行,要么不执行,不会执行的中间被打断,也不会再其他处理器上并行做这个操作。 - 互斥锁本身也是临界资源,如何保护这份临界资源呢,必须保证加锁和解锁是原子性的,互斥锁相当于二元信号量,使用场景不同,二元信号量适用进程,互斥锁适用线程,进程1申请锁成功,但是被切出去,但它是抱着锁出去的(可以这么通俗理解),进程2申请不到锁,只有等进程1在切回来,完成它的操作,然后释放锁,这样就保证了对数据的操作是原子性的了。
死锁的概念
死锁,定义为一组 相互竞争系统资源或进行通信的进程间的“永久”阻塞。
可以概括为所有的死锁问题都涉及两个或多个进程之间对资源需求的冲突。
看了上面的概念,可能还是不太能理解什么叫死锁?
所以下面我们再用通俗点的话来解释。
为什么会产生死锁
死锁:是指两个或者两个以上的进程(线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程)。
那既然有这个问题我们该怎么解决呢?我们首先应该知道死锁产生的条件,然后才可以解决
死锁产生的四个重要条件
①、互斥。
(一次只有一个进程可以使用一个资源,其他进程不能访问已分配给其他进程的资源)。
②、占有且等待。(请求且保持)
当一个进程等待其他进程时(其他资源),继续占有已经分配的资源。
③、不可抢占。
不能强行抢占进程已占有的资源。
(当一个进程的优先级高于一个占有资源的进程的优先级时,并不能强行抢占)。
前三个条件都只是死锁存在的必要条件,但不是充分条件。对死锁的产生,还需要第四个条件。
④、循环等待。(取决于涉及到的进程请求和释放资源的顺序)
存在一个封闭的进程链,使得每个进程至少占有此链中下一个进程所需要的一个资源。
如下图
怎么解决死锁
操作系统中的三种解决方法:死锁检测、预防和避免。
①、预防:
资源分配策略:保守的,预提交资源。
采用某种策略来消除上面4个条件中的其中一个条件的出现来预防死锁。(预防)
②、避免:
处于检测和预防中间(不需要抢占)
基于资源分配的当前状态做动态选择来避免死锁。
③、检测:
非常自由,只要可能,请求的资源都允许。
试图检测死锁的存在并且试图从死锁中恢复出来。
试图破坏四个条件中某一个条件
- 破坏互斥条件:
如果某个资源对于多个进程来说,是共享的,并且可以同时访问的,那么就不会发生死锁。但是一般来说,对于某一个资源必须进行互斥访问,所以说这个条件一般不可能破坏。
如果需要对资源进行互斥访问,那么操作系统必须支持互斥。 - 预防占有且等待条件:
为预防这个条件,可以要求进程一次性的请求所有需要的资源,并且阻塞这个进程直到所有请求都同时满足。
这种方法在两个方面是低效的。
一个进程可能被阻塞很长时间,以等待满足其所有的资源请求。而实际上,只要有一部分资源,它就可以继续执行。
分配给进程的资源可能有相当长的一段时间不会被使用,且在此期间,它们不能被其他进程使用。
一个进程可能事先并不会知道它所需要的所有资源。
3. 预防不可抢占条件:
①如果占有某些资源的一个进程进行进一步资源请求被拒绝,则该进程必须释放它最初占有的资源,如果有必要,可再次请求这些资源和另外的资源。
②、如果一个进程请求当前被另一个进程占有的一个资源,则操作系统可以抢占另一个进程,要求它释放资源。(要求进程的优先级都不相同)。
只有在资源状态可以很容易地保存和恢复的情况下,这种方法才是实用的。
4. 预防循环等待条件:
可以通过定义资源类型的线性顺序来预防。
如果一个进程已经分配到了R类型的资源,那么它接下来请求的资源只能是那些排在R类型之后的资源类型。
解决死锁问题的另一种方法是死锁避免。(区分死锁预防)
在死锁预防中,通过约束资源请求,防止4个死锁条件中至少一个的发生。
这可以通过防止发生三个必要策略条件中的一个间接完成,也可以通过防止循环等待直接完成,但这都会导致低效的资源使用和低效的进程执行。
死锁避免则相反,它允许三个必要条件,但通过明智的选择,确保永远不会到达死锁点,因此死锁避免比死锁预防允许更多的并发。
在死锁避免中,是否允许当前的资源分配请求是通过判断该请求是否可能导致死锁来决定的。
因此,死锁避免需要知道将来的进程资源请求的情况。
死锁避免的两种方法:
如果一个进程的请求会导致死锁,则不启动此进程。
如果一个进程的增加的资源请求会导致死锁,则不允许此分配。
死锁避免的优点是它不需要死锁预防中的抢占和回滚进程,并且比死锁预防的限制少。但是,它在使用中也有很多限制:
必须事先声明每个进程请求的最大资源。
考虑的进程必须是无关的,也就是说,它们执行的顺序必须没有任何同步要求的限制。
分配的资源数目必须是固定的。
在占有资源时,进程不能退出。
- 死锁检测:
死锁预防策略是非常保守的,它们通过限制访问资源和在进程上强加约束来解决死锁问题。
死锁检测策略则完全相反,它不限制资源访问或约束进程行为。
对于死锁检测来说,只要有可能,被请求的资源就被授权给进程。操作系统周期性的执行一个算法来检测前面的循环等待条件。 - 恢复:
一旦检测到死锁,就需要某种策略以恢复死锁。
下面列出可能的方法:
- 取消所有的死锁进程。(操作系统中最长采用的)
- 把每个死锁进程回滚到前面定义的某些检查点,并且重新启动所有进程。这要求在系统中构造回滚和重启机制。该方法的风险是原来的死锁可能再次发生。但是,并发进程的不确定性通常能保证不会发生这种情况。
- 连续取消死锁进程直到不再存在死锁。选择取消进程的顺序基于某种最小代价原则。在每次取消后,必须重新调用检测算法,以测试是否仍存在死锁。
- 连续抢占资源直到不再存在死锁。需要使用一种基于代价的选择方法,并且需要在每次抢占后需要重新调用检测算法。一个资源被抢占的进程必须回滚到获得这个资源之前的某一状态.
对于上面的3和4中的选择算法可以采用下面中的一种: - 取消所有的死锁进程。(操作系统中最长采用的)
- 目前为止消耗的处理器时间最少
- 目前为止产生的输出最少
- 目前为止分配的资源总量最少
- 优先级最低。
可交换空间:在进程交换中所使用的外存中的存储块。
- 可采用以下策略:
通过要求一次性分配所有请求的资源来预防死锁,就像占有且等待预防策略一样。
如果知道最大存储需求,则这个策略是合理的。死锁避免也是可能的。
*进程资源:可分配的设备,如磁带设备和文件。 - 可采用的策略:
对这类资源,死锁避免策略常常是很有效的,这是因为进程可以事先声明它们将需要的这类资源。采用资源排序的预防策略也是可能的。 - 内存:可以按页或按段分配给进程。
对于内存,基于抢占的预防是最适合的策略。当一个进程被抢占后,它仅仅被换到外存,释放空间以解决死锁。 - 内部资源:
诸如I/O通道.
可以使用基于资源排序的预防策略。
死锁总结
死锁是指一组争用系统资源或相互通信的进程被阻塞的现象。
阻塞是永久的,除非操作系统采取某些非常的行动,如杀死一个或多个进程,或者强迫一个或多个进程沿原路返回。
处理死锁通常有三种方法:预防、检测和避免。
死锁预防通过破坏死锁的三个必要条件中的某一个不满足,从而保证不会发生死锁。
如果操作系统总是同一资源请求,则需要死锁检测。操作系统必须周期性地检测死锁,并采取行动破坏死锁。