死锁

来源:互联网 发布:白夜追凶剧情分析知乎 编辑:程序博客网 时间:2024/06/03 03:40

死锁

所谓死锁就是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。现实生活中简单的例子:交通阻塞,两股相向而行的车流都想通过已经被对方占用的道路(类似独木桥),结果双方都不能前进。

死锁产生的必要条件:

产生死锁必须同时满足以下四个条件,只要其中任意一条不成立,死锁就不会发生。
(1)互斥条件:进程要求对所分配的资源进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时,若有其他进程请求该资源,则请求进程只能等待。
(2)不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该进程的资源自己来释放。
(3)请求和保持条件:又称为部分分配条件。进程每次申请他所需要的一部分资源,在等待新资源的同时,进程继续占有已经分配到的资源。
(4)循环等待条件:存在一种进程资源的循环等待链,链中每个进程所获得的资源同会被链中的下一个进程所请求。即存在一个处于等待状态的进程集合{p1,p2,p3,…,pn},其中pi等待资源被pi+1占有,pn等待资源被p0占有。
典型的请求和保持:

这里写图片描述

预防死锁的方法:

(1)打破互斥条件。即允许进程同时访问某些资源。但是有的资源是不能被同时访问的,比如打印机等等,这是有资源本身的属性所决定的。所以这种办法并无实用价值。
(2)打破不可剥夺条件。即允许进程强行从占有者那里夺取某些资源。即某进程已经占有了某些资源。它又申请新的资源。但不能立即被满足时,它必须释放所占有的全部资源,以后再重新申请。它所释放的资源可以分配给其他进程。这就相当于该进程占有的资源被隐蔽的强占了,这种预防死锁的方法实现起来困难,会降低系统性能。
(3)打破请求和保持条件。可以实行资源预先分配策略。即进程在运行前一次性的向系统申请它所需要的全部资源。如果某个进程所需要的全部资源得不到满足,则不分配任何资源,此进程暂不运行。只有当系统能够满足当前进程的全部资源需求时,才一次性将所申请的资源全部分配给该进程。由于运行的进程已占有了它所需的全部资源,所以不会发生占有资源又申请资源的情况。
(4)打破循环等待条件,实行资源有序分配策略。即把资源事先分类编号,按号分配,使进程在申请,占有资源时不会形成环路。所有进程对资源的请求必须严格按照资源序号递增的顺序提出。进程占用了小号资源,才能申请大号资源,就不会产生环路,从而预防了死锁。

避免死锁的方法:

在资源的动态分配过程中,用某种方法避免进程进入不安全状态。银行家算法是著名的死锁避免算法。  
所谓的银行家算法就是:一个银行家的借贷业务,必须保证其借贷人在一定时间内归还本金,以至于资金可以顺利周转,不至于倒闭。
在操作系统中,操作系统就相当于银行家,所有的资源就是他的本金。系统中有限的资源要供多个进程使用,必须保证得到资源的进程能在有限的时间内归还资源,以供其他进程使用资源。如果资源分配不到就会发生进程循环等待资源,则进程都无法继续执行下去的死锁现象。

死锁的检测和解除:

无须采取任何限制性措施,允许进程在运行过程中发生死锁,通过系统的检测机制及时的检测出死锁的发生,然后采取某种措施解除死锁。死锁的检测可以利用资源分配图来描述。死锁的解除主要方法如下:1)资源剥夺法。2)撤销进程法。3)进程回退法。

简单的线程锁的实现:

#include<stdio.h>#include<unistd.h>#include<stdlib.h>#include<sys/types.h>#include<pthread.h>pthread_mutex_t mutex;pthread_t id1,id2;int i = 0;//必须使用多线程void *fun1(void *arg){    int count = 50000;    while(count--)    {        pthread_mutex_lock(&mutex);        i++;        pthread_mutex_unlock(&mutex);    }   }void *fun2(void *arg){    int count = 50000;    while(count--)    {        pthread_mutex_lock(&mutex);        i++;        pthread_mutex_unlock(&mutex);    }}int main(){    pthread_create(&id1,NULL,fun1,(void*)NULL);    pthread_create(&id2,NULL,fun2,(void*)NULL);    pthread_join(id1,NULL);    pthread_join(id2,NULL);    printf("i = %d\n",i);    return 0;}

验证i++是否为原子操作,设置CPU核数为2.达到两个线程并行,若不加锁i的终值始终无法达到100000,对(i++)使用互斥锁,可以达到100000。

i++不是原子操作。
i++分为三个阶段
读取i值到寄存器
寄存器自增
写回内存

这三个阶段的任何一步都可以中断分离.

汇编:

{    mov eax,dword ptr[i]    inc eax    mov dword[i],eax}

此种情况下,必定不是原子操作,不加锁互斥是不行的。
假设加了优化参数,那么是否一定编译成为”inc dword ptr[i]”呢?答案是否定的,要看具体的编译器(不将i值写入寄存器而是直接操作内存)。
若在单核机器上,不加锁是inc dword ptr[i]是不会有问题的,但是在多核机器上,两个CPU同时执行inc指令,但是两条指令执行以后,却可能只出现自加一次的结果。
真正可以确保不额外加锁的汇编指令是”lock inc dword ptr[i]”,lock前缀可以暂时锁住总线,这时候其他CPU是无法访问响应数据的。但是目前没有任何一个编译器会将i++编译成这种形式。