死锁 是指多个并发进程在运行过程中因竞争资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,这些进程都无法向前推进。
- 并发性 : 指两个或多个事件在同一时刻发生
- 并行性 : 指两个或多个事件在同一时间间隔内发生
来看两种典型的死锁情形:
- 如果同⼀个线程先后两次调⽤lock,在第⼆次调⽤时,由于锁已经被占⽤,该线程会挂起等待别的线程释放锁,然⽽锁正是被⾃⼰占⽤着的,该线程又被挂起⽽没有机会释放锁,因此 就永远处于挂起等待状态了。
- 线程A获得了锁1,线程B获得了锁2,这时线程A调⽤lock试图获得锁2,结果是需要挂起等待线程B释放 锁2,⽽这时线程B也调⽤lock试图获得锁1,结果是需要挂起等待线程A释放锁1,于是线程A和B都 永远处于挂起状态了。
产生死锁的原因
由上述两种情形,可知,产生死锁的原因如下:
1. 系统资源不足
2. 进程(线程)推进的顺序不当;
3. 资源分配不当
产生死锁的必要条件
进程(线程)运行过程中可能发生死锁,但死锁的产生也需要具备必要的条件:
(1)互斥条件:一个资源每次只能被一个进程使用。
(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3)不可剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
如果涉及到更多的线程和更多的锁,可能死锁的问题将会变得复杂和难以判断。
写程序时应该尽量避免同时获得多个锁,如果⼀定有必要这么做,则有⼀个原则 : 如果所有线程在需要多个锁时都按相同的先后顺序(常见的是按Mutex变量的地址顺序)获得锁,则不会出现死锁。⽐如⼀个程序中⽤到锁1、锁2、锁3,它们所对应的Mutex变量的地址是锁1<锁2<锁3, 那么所有线程在需要同时获得2个或3个锁时都应该按锁1、锁2、锁3的顺序获得。如果要为所有的锁确定⼀个先后顺序⽐较困难,则应该尽量使⽤pthread_mutex_trylock(该函数申请mutex失败时返回EBUSY)调⽤代替pthread_mutex_lock(该函数申请mutex失败时直接挂起等待,直到被唤醒) 调⽤,以免死锁。
死锁的避免
前面提到死锁的产生需要满足其必要条件,那么死锁的避免就要破坏其必要条件。
* 注意:死锁的互斥条件不可破坏,进程(线程)对临界资源的访问必须保证互斥。
1. 摒弃“请求和保持”条件
2. 摒弃“不剥夺”条件
3. 摒弃“环路等待”条件
基本思想 :
允许三个必要条件存在。系统在进行资源分配之前,应先计算此次资源分配后状态的安全性。若此次分配后的状态是安全状态,则将资源分配给进程;否则,令进程等待。
避免死锁的算法:
银行家算法
银行家算法
算法原理
我们可以把操作系统看作是银行家,操作系统管理的资源相当于银行家管理的资金,进程向操作系统请求分配资源相当于用户向银行家贷款。
为保证资金的安全,银行家规定:
- 当一个顾客对资金的最大需求量不超过银行家现有的资金时就可接纳该顾客;
- 顾客可以分期贷款,但贷款的总数不能超过最大需求量;
- 当银行家现有的资金不能满足顾客尚需的贷款数额时,对顾客的贷款可推迟支付,但总能使顾客在有限的时间里得到贷款;
- 当顾客得到所需的全部资金后,一定能在有限的时间里归还所有的资金.
有关银行家算法的详细介绍见博客银行家算法避免死锁问题代码实现