关于阻塞/非阻塞、同步/非同步、死锁

1、阻塞/非阻塞、同步/非同步

同步与非同步(消息通知机制):关注的是等待过程的通知方式需要自己主动询问还是被动通知。

阻塞与非阻塞(等待消息通知时的状态):关注的是等待过程是否可以转变为其他非等待状态。

举例说明:假设我要下载一个视频

1、通过看下载进度条等待下载完成的结果(同步),期间不做其他事情(阻塞)

2、通过看下载进度条等待下载完成的结果(同步),期间去聊QQ,在聊QQ的时候不停地去看下载是否完成(非阻塞)

3、通过下载完成的提示音通知得到下载完成的结果(异步),期间不做其他事情(阻塞)

4、通过下载完成的提示音通知得到下载完成的结果(异步),期间去聊QQ,在聊QQ的时候不需要去看下载是否完成,因为下载完了提示音会通知我(非阻塞)

论这四个概念的时候一定要放在同一个层级,比如操作系统级别,框架级别,业务代码级别等,因为一个事件在不同层级所属的性质不一定一样,只有在同一个层级,才能去讨论它的性质是同步/异步还是阻塞/非阻塞。

阻塞与非阻塞并没有好坏之分,有不同的应用场景,比如在阻塞期间会多次尝试,但是阻塞不能做其他事情,效率不高。非阻塞能做其他事情,但是也会增加线程切换的开销。

2、死锁

2.1 死锁的四个必要条件

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

以下四个条件是死锁发生的必要条件,缺一不可,如果发生了死锁则下列条件一定全部满足:四个状态同时满足就是不安全状态,但是进入不安全状态并不一定会发生死锁;

1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

2.2 避免死锁

避免死锁就是打破上述 四个必要条件至少一个:

(1)打破互斥条件:运通多个进程同时访问(有些资源是独占资源,例如打印机,无法共享)

(2)打破请求保持条件:预先一次性分配所有资源,如果缺少则全部不分配。

(3)打破不剥夺条件:允许进程从资源占有者哪里夺取资源,其实是自己已经有资源A,去申请资源B,但是没有成功,此时释放资源A分配给其他进程,也就相当于其他进程隐性抢占了资源A。

(4)打破环路等待条件:实施资源有序分配。所有资源实现编号,按照号从小到大进行分配,所有程序都是在占有小号资源的前提下才能申请大号资源,从而不会形成环路。

 

2.3 死锁避免算法

安全序列:安全序列{P1,P2,...,Pn}是这样组成的:若对于每一个进程Pi,它需要的附加资源可以被系统中当前可用资源加上所有进程Pj当前占有资源之和所满足,则{P1,P2,...,Pn}为一个安全序列,这时系统处于安全状态,不会进入死锁状态。 ????不太懂
银行家算法:一个银行家拥有一定数量的资金,有若干个客户要贷款。每个客户须在一开始就声明他所需贷款的总额。若该客户贷款总额不超过银行家的资金总数,银行家可以接收客户的要求。客户贷款是以每次一个资金单位(如1万RMB等)的方式进行的,客户在借满所需的全部单位款额之前可能会等待,但银行家须保证这种等待是有限的,可完成的。

对于a图的状态,按照安全序列的要求,我们选的第一个客户应满足该客户所需的贷款小于等于银行家当前所剩余的钱款,可以看出只有C2客户能被满足:C2客户需1个资金单位,小银行家手中的2个资金单位,于是银行家把1个资金单位借C2客户,使之完成工作并归还所借的3个资金单位的钱,进入b图。同理,银行家把4个资金单位借给C3客户,使其完成工作,在c图中,只剩一个客户C1,它需7个资金单位,这时银行家有8个资金单位,所以C1也能顺利借到钱并完成工作。最后(见图d)银行家收回全部10个资金单位,保证不赔本。那麽客户序列{C1,C2,C3}就是个安全序列,按照这个序列贷款,银行家才是安全的。否则的话,若在图b状态时,银行家把手中的4个资金单位借给了C1,则出现不安全状态:这时C1,C3均不能完成工作,而银行家手中又没有钱了,系统陷入僵持局面,银行家也不能收回投资。

综上所述,银行家算法是从当前状态出发,逐个按安全序列检查各客户谁能完成其工作,然后假定其完成工作且归还全部贷款,再进而检查下一个能完成工作的客户,......。如果所有客户都能完成工作,则找到一个安全序列,银行家才是安全的。

  从上面分析看出,银行家算法允许互斥条件,占有且申请条件,不可抢占条件的存在,这样,它与预防死锁的几种方法相比较,限制条件少了,资源利用程度提高了

这是该算法的优点。其缺点是:

   〈1〉这个算法要求客户数保持固定不变,这在多道程序系统中是难以做到的。   

   〈2〉这个算法保证所有客户在有限的时间内得到满足,但实时客户要求快速响应,所以要考虑这个因素。  

    〈3〉由于要寻找一个安全序列,实际上增加了系统的开销

2.4 死锁的检测与恢复

2.4.1 死锁检测

  一个简单却无效率的方法:有两个容器,一个用于保存线程正在请求的锁,一个用于保存线程已经持有的锁。每次加锁之前都会做如下检测:

  1)检测当前正在请求的锁是否已经被其它线程持有,如果有,则把那些线程找出来;

  2)遍历第一步中返回的线程,检查自己持有的锁是否正被其中任何一个线程请求。如果第二步返回真,表示出现了死锁。

  java中的Thread类中的holdslock()方法。

2.4.2 死锁恢复

   一旦在死锁检测时发现了死锁,就要消除死锁,使系统从死锁状态中恢复过来。  

    (1)最简单,最常用的方法就是进行系统的重新启动,不过这种方法代价很大,它意味着在这之前所有的进程已经完成的计算工作都将付之东流,包括参与死锁的那些进程,以及未参与死锁的进程。

    (2)撤消进程,剥夺资源。终止参与死锁的进程,收回它们占有的资源,从而解除死锁。这时又分两种情况:一次性撤消参与死锁的全部进程,剥夺全部资源;或者逐步撤消参与死锁的进程,逐步收回死锁进程占有的资源。一般来说,选择逐步撤消的进程时要按照一定的原则进行,目的是撤消那些代价最小的进程,比如按进程的优先级确定进程的代价;考虑进程运行时的代价和与此进程相关的外部作业的代价等因素。 

    此外,还有进程回退策略,即让参与死锁的进程回退到没有发生死锁前某一点处,并由此点处继续执行,以求再次执行时不再发生死锁。虽然这是个较理想的办法,但是操作起来系统开销极大,要有堆栈这样的机构记录进程的每一步变化,以便今后的回退,有时这是无法做到的。

posted @ 2019-09-14 11:33  simpleDi  阅读(565)  评论(0编辑  收藏  举报