Linux 死锁的产生及其避免方法

文章目录

    • 引言
    • 何谓死锁
    • 死锁产生的四个条件
    • 死锁的预防
    • 避免死锁

引言

我们看一个场景---------哲学家吃饭的故事

哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。当他们饿的时候就必须吃饭,每个人只能使用自己左右手两边的筷子,哲学家从来不交谈,这就很危险,可能产生死锁,每个哲学家都拿着左手的餐叉,永远都在等右边的餐叉(或者相反)。就会导致最后大家都吃不了饭

何谓死锁

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

通俗的说,就是线程A占有自己的资源,线程B也有自己的资源,线程A需要B的资源才能继续运行,而线程B也需要线程A的资源才能继续运行,但两者都不释放自己占有的资源,就会互相等待对方的资源,进而产生死锁。(就和上面哲学家吃饭的故事一样)

死锁产生的四个条件

1. 互斥条件
    一块资源一次只能同时被一个线程访问,即一旦该资源分配给某个线程,其他任何线程都不能再访问这块资源,直到占有资源的线程将其释放。
    通俗理解:我加锁了别人不能加锁
2. 请求与保持条件
    线程已将占有一块资源,但又提出新的资源请求,而申请的资源正在被其他线程使用,此时请求将阻塞,但又对自己原有的资源不进行释放。
    通俗理解:拿着第一个请求第二个,拿不到第二个但是第一个也不放手(吃着碗里的看着锅里的)
3. 不可剥夺条件
    线程已获得的某个资源,在没有别被释放之前,不能被其他任何线程剥夺,只能自己释放
    通俗理解:我的锁别人不能解锁
4. 循环等待条件
    指在发生死锁时,资源的环形链,即线程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

死锁的预防

其实,预防死锁很简单,只需要破坏上述四个条件中的其一即可。但由于互斥条件是非共享资源所必须的,不仅不能改变,还必须加以保证,所以只能改其他三个条件。

1. 打破请求与保持条件
采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待。
优点:简单易实施
缺点:因为某项资源不满足,线程无法启动,而其他已经满足了的资源也不会得到利用,严重降低了资源的利用率,造成资源浪费。

2. 打破不可剥夺条件
当一个线程已经占有了一份资源,再次申请另一份资源时,必须先释放原来占有的资源,等需要的时候再申请

3. 打破循环等待条件
实现资源有序分配策略,对所有设备实现分类编号,所有进程只能采用按序号递增的形式申请资源。

避免死锁

a. 加锁顺序
当多个线程需要相同的锁,同时获取,可能会获取到不同的锁,就很容易产生死锁
比如,线程A和线程B都需要锁1和锁2,如果线程A获取到了锁1,同时线程B获取到了锁2,就会死锁。
解决方法:确保所有的线程都必须按照规定的顺序来获取锁。比如,线程B想要获取到锁2,就必须先要获取锁1,这样就不会产生死锁了。

b. 加锁时限

在尝试获取锁的时候加一个超时时间,这也就意味着在尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求。若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。

c. 银行家算法
我们可以把操作系统看作是银行家,操作系统管理的资源相当于银行家管理的资金,进程向操作系统请求分配资源相当于用户向银行家贷款。

为保证资金的安全,银行家规定:
(1) 当一个顾客对资金的最大需求量不超过银行家现有的资金时就可接纳该顾客;
(2) 顾客可以分期贷款,但贷款的总数不能超过最大需求量;
(3) 当银行家现有的资金不能满足顾客尚需的贷款数额时,对顾客的贷款可推迟支付,但总能使顾客在有限的时间里得到贷款;
(4) 当顾客得到所需的全部资金后,一定能在有限的时间里归还所有的资金.

操作系统按照银行家制定的规则为进程分配资源,当进程首次申请资源时,要测试该进程对资源的最大需求量,如果系统现存的资源可以满足它的最大需求量则按当前的申请量分配资源,否则就推迟分配。当进程在执行中继续申请资源时,先测试该进程本次申请的资源数是否超过了该资源所剩余的总量。若超过则拒绝分配资源,若能满足则按当前的申请量分配资源,否则也要推迟分配。

d. 死锁检测算法

每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。

当一个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发生。例如,线程A请求锁2,但是锁2这个时候被线程B持有,这时线程A就可以检查一下线程B是否已经请求了线程A当前所持有的锁。如果线程B确实有这样的请求,那么就是发生了死锁(线程A拥有锁1,请求锁2;线程B拥有锁2,请求锁1)。

当然,死锁一般要比两个线程互相持有对方的锁这种情况要复杂的多。线程A等待线程B,线程B等待线程C,线程C等待线程D,线程D又在等待线程A。线程A为了检测死锁,它需要递进地检测所有被B请求的锁。从线程B所请求的锁开始,线程A找到了线程C,然后又找到了线程D,发现线程D请求的锁被线程A自己持有着。这是它就知道发生了死锁。

那么该如何解决??
一种可行的方法就是释放所有的锁,简单粗暴;
另一种方法就是逐个终止进程,直到死锁的状态解除。

你可能感兴趣的:(Linux)