简介
信号量是一种用于提供不同进程或者一个进程内部不同线程间同步的一种机制“同步”:并发的实体直接,相互制约,相互等待的一种机制,保证实体对共享资源的有条件的访问,信号量就是为了保护共享资源,让共享资源有序访问的一种机制
信号量类似于计数器,是一个特殊的变量,值可以改变,但只能取正整数值,并且对它的加 1 和减 1 操作是原子操作。如果信号量值为 0,那么再进行减 1 操作时会阻塞。信号量的初始值,代表资源的数量。
作用:控制多个进程对临界资源的访问,使程序在同一个时刻,只有一个进程访问临界资源(进行进程间同步控制)。原理就是控制程序的执行速度。信号量保护共享资源是通过:在访问临界区前加一个 P 操作,在访问临界区后加一个 V操作
死锁思考
1.现在有 5 个独立的共享资源 A,B,C,D,E 需要保护,程序员决定使用一个信号量 S 来保护这 5 个资源,如果采用下面设计方式
对 A 资源
P(S)
A
V(S)
对 B 资源
P(S)
B
V(S)
....
请问这种设计有问题吗?
答:虽然可以达到“保护”的目的,但是降低了并发度,因为 A 和 B 本身是可以同时访问的
2.现在有 5 个独立的共享资源 A,B,C,D,E 需要保护,程序员决定使用五个信号量S1,S2,S3...S5 来保护这 5 个资源,如下面设计
S1--->A
S2--->B
.....
对 A 资源
P(S1)
A 的临界区
V(S1)
对 B 资源
P(S2)
B 的临界区
V(S2)
...
那么就有一个问题?
如果有一个进程需要同时访问 A 和 B(A 和 B 的临界区)
那么就会出现
P1 进程:
P(S1)
P(S2)
//访问 A 和 B 的临界区
V(S2)
V(S1)
P2 进程:
P(S2)
P(S1)
//访问 A 和 B 的临界区
V(S1)
V(S2)
这样在 P1 进程想要获取 P(S2)时,但是此时 P(S2)还在 P2 手上,而 P2 在等 P(S1),而P(S1)在 P1 手上,这种设计就会造成死锁(deadlock)
,没有外力情况下,状态永远不会改变。
最简单的改变就是让信号量的锁同时被获取,同时被释放,如下设计
P1 进程
P(S1 & S2) //要么同时获取,要么都不获取
//访问 A 和 B 的临界区
V(S1 & S2)
如何避免死锁
1.有顺序合理的设置锁
2.设置超时放弃
3.银行家算法
如何检测死锁
一个简单的死锁检测方法,准备两张表。一张记录当前资源的分配,有线程和它占有的资源的对应。另一张记录当前等待资源的线程,有等待的线程和它请求的资源对应。当能够检测到环路时,表示发生了死锁。
死锁的解除
1.终止所有死锁进程
2.逐个终止进程,直到死锁解除
3.事先规定好进程被抢占的顺序以使代价最小。(要让各进程有限的被选为“牺牲品”,否则某些经常被强占的进程会发生“饥饿”现象。),发生死锁时,选择一个“牺牲品”,抢占它的资源给其它进程;然后将这个“牺牲品”进程回滚到过去的某个安全状态。
“回滚”
: 采用这种方式时,系统会周期性地对进程进行状态记录,包括存储映像、资源状态等,都写入到一个单独的文件中。发生死锁时,系统就将牺牲品进程回滚到它的一个较早的安全状态。
银行家算法
银行家算法是一个可以避免死锁
的著名算法,在避免
死锁
方法中允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次分配资源的安全性,若分配不会导致系统进入不安全状态,则分配,否则等待。为实现银行家算法,系统必须设置若干数据结构
。
如下:
1)可利用资源向量 Available
是个含有 m
个元素的
数组
,其中的每一个元素代表一类可利用的资源数目。如果Available[j]=K,则表示系统中现有
Rj
类资源
K
个。
2)最大需求矩阵 Max
这是一个 n×m
的
矩阵
,它定义了系统中
n
个进程中的每一个进程对
m
类资源的最大需求。如果 Max[i,j]=K
,则表示进程
i
需要
Rj
类资源的最大数目为
K
。
3
)分配
矩阵
Allocation
这也是一个 n×m
的
矩阵
,它定义了系统中每一类资源当前已分配给每一进程的资源数。如果 Allocation[i,j]=K
,则表示进程
i
当前已分得
Rj
类资源的 数目为
K
。
4
)需求
矩阵
Need
。
这也是一个 n×m
的
矩阵
,用以表示每一个进程尚需的各类资源数。如果Need[i,j]=K,则表示进程
i
还需要
Rj
类资源
K
个,方能完成其任务。
Need[i,j]=Max[i,j]-Allocation[i,j]
在了解银行家算法之前,我们来了解一下什么是安全状态,什么是不安全状态。
我们来看一个
实例
:
假如操作系统中的 4 个进程 P1、P2、P3、P4 和 3 类资源 R1、R2、R3(资源数量分别为 9、3、6),在 t0 时刻它们的资源分配情况如下标
max
:表示该进程所需的最大的资源数
allocation
:表示进程现在所拥有的资源数
need
:表示进程还需要的资源数
available
:表示系统中可用的资源数
那么分析上图,回答在这个时刻是安全状态吗?
这个时候 available(1,1,2)显然不能满足 P1 的需求,所以寻找第一个可用满足需求的进程,于是找到了 P2,满足了 P2 之后,P2 进程任务完成后资源就释放了,然后就变成了 available(6,2,3),之后依据系统可用资源依次寻找第一个可以满足的条件的进程,由此得到一个安全序列{P2,P1,P3,P4},故系统是安全的。
接着思考,如果在这个时刻 P2 又发来了一个资源请求向量 request(1,0,1),系统会接收它
吗?
当 P2 进程发送 request(1,0,1)资源请求后,银行家算法会对此做检查满足条件 request(1,0,1) < need(1,0,2),因为进程此刻请求的资源数不能大于此刻还需要完成任务的进程数,保证进程所需最大资源数不变,同时满足 request(1,0,1) < available(1,1,2),保证系统中还剩下的资源数能够满足要求,全部满足后,假定为 P2 分配了资源,则资源表就变为如下
用上面的安全性分析的方法分析这个表,还是可以得到一个安全的序列{P2,P1,P3,P4},则 P2 的 request(1,0,1)资源请求会被接收
以上例子就是银行家算法的思想。我们可以把操作系统
看作是银行家,操作系统管理的资源相当于银行家管理的资金,进程向操作系统请求分配资源相当于用户向银行家贷款。
为保证资金的安全,银行家规定:
(1) 当一个顾客对资金的最大需求量不超过银行家现有的资金时就可接纳该顾客;
(2) 顾客可以分期贷款,但贷款的总数不能超过最大需求量;
(3) 当银行家现有的资金不能满足顾客尚需的贷款数额时,对顾客的贷款可推迟支付,但总能使顾客在有限的时间里得到贷款;
(4) 当顾客得到所需的全部资金后,一定能在有限的时间里归还所有的资金.
操作系统按照银行家制定的规则为进程分配资源,当进程首次申请资源时,要测试该进程对资源的最大需求量,如果系统现存的资源可以满足它的最大需求量则按当前的申请量分配资源,否则就推迟分配。当进程在执行中继续申请资源时,先测试该进程本次申请的资源数是否超过了该资源所剩余的总量。若超过则拒绝分配资源,若能满足则按当前的申请量分配资源,否则也要推迟分配。
算法示例:
#include<iostream>
using namespace std;
// p 进程数,r 资源种类
int p ;
int r ;
int maxs[10][10]; //最大需求矩阵
int allocation[10][10]; //分配矩阵
int need[10][10]; //需求矩阵
int available[10]; //可用资源向量
int request[10]; //请求向量当前进程对各类资源的申请量,算法的入口参数
//输入函数
void infInput()
{
int i,j;
cout<<"请输入最大需求矩阵 max\n";
for(i=0; i<p; i++)
{
for(j=0; j<r; j++)
{
cin>>maxs[i][j];
}
}
cout<<"请输入分配矩阵 allocation\n";
for(i=0; i<p; i++)
{
for(j=0; j<r; j++)
{
cin>>allocation[i][j];
}
}
cout<<"请输入需求矩阵 need\n";
for(i=0; i<p; i++)
{
for(j=0; j<r; j++)
{
cin>>need[i][j];
}
}
cout<<"请输入可用资源向量 available\n";
for(i=0; i<r; i++)
{
cin>>available[i];
}
}
//比较函数
//比较进程为 m 中的元素全大于 n 中的元素返回 1,否则返回 0
int compare(int m[],int n[])
{
int i;
for(i=0; i<r; i++)
{
if(m[i]<n[i])
{
return 0;
}
}
return 1;
}
//安全性检验函数,检测是否存在安全序列
int stest()
{
int i,j,k,l,flag=0;
int finish[p]; //用来判断系统可用资源满足哪个进程所需
int work[r]; //临时保存 available 的值
for(i=0; i<p; i++)
{
finish[i]=0;
//finish[i]为 1 即表示 available 满足第 i 进程的资源需要
}
for(i=0; i<r; i++)
{
work[i]=available[i];
}
cout<<"分配序列:\n";
cout<<" allocation need avil
able"<<endl;
for(k=0; k<p; k++)
{
for(i=0; i<p; i++)
{
if(finish[i]==1)
{
continue;
}
else
{
if(compare(work,need[i]))//available>=need
{
finish[i]=1; //标志置 1,表示这个进程被满足
cout<<'\n'<<"进程"<<i+1<<'\t';
flag=1; //flag 置 1,让系统资源再从第一个进程开始遍历
for (j =0; j<r; j++)
{
printf(" %2d ", allocation[i][j]);
}
cout<<" ";
for (j = 0; j < r; j++)
{
printf(" %2d ", need[i][j]);
}
cout<<" ";
for (j = 0; j <r; j++)
{
printf(" %2d ", work[j] +allocation[i][j]);
}
for(l=0; l<r; l++)
{
work[l]=work[l]+allocation[i][l];//进程完成,释放资源
}
break;
}
}
if(flag==1)
{
break;
}
}
}
cout<<'\n';
for(l=0; l<p; l++)
{
if(finish[l]==0)
{
return 0;//不存在安全序列
}
}
return 1;//存在安全序列
}
//申请进程后的安全性检验函数
void rtest(int n)
{
int j;
//n=n-1;
if(compare(available,request)&&compare(need[n-1],request))//available>=request 并且 need >=request
{
for(j=0; j<r; j++) //重新分配资源
{
allocation[n-1][j]=allocation[n-1][j]+request[j];
need[n-1][j]=need[n-1][j]-request[j];
available[j]=available[j]-request[j];
}
if(stest())
{
cout<<"允许"<<n<<"进程申请资源\n";
}
else
{
cout<<"不允许"<<n<<"进程申请资源\n";
for(j=0; j<r; j++) //回退到安全状态
{
allocation[n-1][j]=allocation[n-1][j]-request[j];
need[n-1][j]=need[n-1][j]+request[j];
available[j]=available[j]+request[j];
}
}
}
else
{
cout<<"申请资源量越界\n";
}
}
int main()
{
int i,n; //n-第 n 个资源申请
cout<<"请输入进程数:";
cin>>p;
cout<<"请输入资源种类数:";
cin>>r;
infInput();//矩阵输入函数
if(stest()==1) //银行家算法检测
{
cout<<"存在安全序列,初始状态安全。\n";
}
else
{
cout<<"不存在安全序列,初始状态不安全。\n";
}
cout<<"请输入发出请求向量 request 的进程编号:";
cin>>n;
cout<<"请输入请求向量 request\n";
for(i=0; i<r; i++)
{
cin>>request[i];
}
rtest(n);
return 0;
}