何为死锁:
现有A、B两个进程和a,b两把锁,a锁保护a资源,b锁保护b资源,现在A申请a资源,则a锁上锁,B申请b资源,则b锁上锁。此时,A又需要b资源,对b资源进行申请,但B正在使用,即b锁已经上锁,没有申请到,即挂起等待,同时,B需要a资源,对a资源进行申请,但A已经挂起,并且A持有a资源,所以B不可能申请到,所以B也 挂起。此时、B都挂起——死锁。
死锁产生的四个必要条件
1、互斥条件——一个资源一次只能被一个进程(或线程)使用;
2、持有——当请求的资源被占用导致执行阻塞时,持有者不仅 可以 不释放资源,还可以申请更多资源(这句话理解为:当前系统共有多个进程或线程申请1这份资源,但1这份资源被A进程拿着,此时等待1资源的进程或线程均被挂起等待,且此时A还可以申请2,3,4,5,6等等资源,这对操作系统来说,完全符合规定);
3、不可剥夺——只有资源持有者才有权释放该资源;
4、环路等待——若干进程或线程以不同的次序获得互斥资源,而形成的环路等待,即当前进程总在等待上一个进程释放的资源。
预防死锁的方法:
产生死锁共有4中方法,根据这四种方法,就可以针对性的进行解决。
1、互斥条件,这是防止多进程、多线程间不产生冲突的根本所在,所以不能进行改变。
2、持有,在创建进程或线程时就一次性分配这么多的资源,如果当前存在这么多资源,则允许其运行,如果没有,则等待;每次在申请新资源时,必须释放他之前所占有的资源。
3、不可剥夺,如果某个进程或线程在持有某个资源的同时,申请另外的资源,则必须释放最初占有的资源,若后续还会用到,则再次申请;或者根据抢占的原理,即再优先级高于对方的情况下,如果对方只是申请资源,并没有使用资源,则可以对这份资源进行抢占。
4、对所有资源进行统一编号。进程读资源的申请必须以资源编号的升序方式提出。
解决死锁的几种算法
1、有序资源分配法
所有资源的使用都要有序的使用,要保证:
1>对它所必须使用的而且属于同一类的所有资源,必须一次申请完;
2>在申请不同类资源时,必须按各类设备的编号依次申请。例如:进程PA,使用资源的顺序是R1,R2; 进程PB,使用资源的顺序 是R2,R1;若采用动态分配有可能形成环路条件,造成死锁。这就可以完美解决开篇的第一个问题。
2、银行家算法
避免死锁算法中最有代表性的算法是Dijkstra E.W 于1968年提出的银行家算法。
所谓的银行家算法就是:一个银行家的借贷业务,必须保证其借贷人在一定时间内归还本金,以至于资金可以顺利周转,不至于倒闭。
在操作系统中,操作系统就相当于银行家,所有的资源就是他的本金。系统中有限的资源要供多个进程使用,必须保证得到资源的进程能在有限的时间内归还资源,以供其他进程使用资源。如果资源分配不得到就会发生进程循环等待资源,则进程都无法继续执行下去的死锁现象。
死锁的预防和恢复
1、资源掠夺法:当前系统中存在多个死锁程序时,选择葫芦一部分死锁程序,并抢占他的资源,将这些稀有分配给其他进程使用,直到死锁不存在为止;
2、撤销进程法:放弃处于死锁进程中的进程,直接撤销该进程,直到死锁不存在为止;
3、进程回退法:进程自按获取顺序自愿放弃获取的资源,系统保存进程的历史信息,设置还原点。
简单的线程锁的实现:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_t tid1,tid2;
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
unsigned int count=0;
void* add1(void *val)
{
int i=5000;
int value=0;
while(i--)
{
pthread_mutex_trylock(&lock);
value=count;
printf("add1:%u count:%u\n",pthread_self(),count);
count=value+1;
pthread_mutex_unlock(&lock);
}
}
void* add2(void *val)
{
int i=5000;
int value=0;
while(i--)
{
pthread_mutex_lock(&lock);
value=count;
printf("add1:%u count:%u\n",pthread_self(),count);
count=value+1;
pthread_mutex_unlock(&lock);
}
}
int main()
{
pthread_create(&tid1,NULL,add1,NULL);
pthread_create(&tid2,NULL,add2,NULL);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
printf("final count is %u\n",count);
}
不加锁的输出:
每次运行过后的结果都有差异,且不会达到10000,因为加一的操作不是原子操作,这个在我之前的博客里面有提过。
加锁后的输出:
多次运算过后始终未10000。
因为此时这里执行的操作为:
而xchgc一步达成!
如有错误,请指出!