哲学家问题
五个哲学家围坐在一圆桌旁,桌中央有一盘通心粉,没人面前有一只空盘子,每两人之间放一只叉子。每个哲学家的行为是思考,感到饥饿,然后吃通心粉。为了吃通心粉,每个哲学家必须拿到两只叉子,并且每个人只能直接从自己的左边或有百年去取叉子
使用P、V操作解决
每一只叉子用一个信号量表示,通过对信号量执行P操作取得叉子,执行V操作放下叉子
设置信号量:
semaphore fork[n];//n=5,公用信号量,用于互斥
fork[i]=1;//设置初值,i=1,2,,, ,5
第i个哲学家进程
while(1){
thingking,,,
P(fork[i])//拿左手的叉子
P(fork[i+1])//拿右手的叉子
eating;
V(fork[i+1])//放下右手的叉子
V(fork[i]) //放下左手的叉子
这样可以确保没有两个相邻的哲学家可以同时进餐
}
带来的问题:
死锁--每个人拿起而且只拿起了一只叉子,并且都不放下,僵在这
饥饿--每个人都拿起了叉子,然后动作很协调又同时放下,反复重复这个过程(长时间得不到响应)
原因:P、V操作本身可以很好的解决同步互斥问题,但是实际中遇到的问题并不都是同步互斥问题,并且P、V操作本身也存在着缺点:操作繁琐,同步互斥操作被分散在各个进程中,使得易读性、维护性差,复杂性高,对于一些复杂问题实现不方便,存在隐患。
如何解决?--提出了一些高级数据结构
信号量集--AND型信号量集
AND型信号量集是指同时需要多种资源且每种占用一个时的信号量操作
AND型信号量集的基本思想:在一个原语中申请整段代码需要的多个临界资源,要么全部分配给它,要么一个都不分配
AND型信号量集P原语为Swait
AND型信号量集V原语为Ssignal
Swait(S1,S2, ,,, ,Sn)//P原语
{
while(TRUE){
if(S1>=1&&S2>=1&& ,,, && Sn>=1){
for(i=1,i<=n;++i){
Si--;
}
break;
}
else{//某些资源不够用的处理
调用进程进入第一个小于1信号量的等待队列,阻塞调用进程;
}
}
}
Ssignal(S1, S2, …, Sn)
{
for (i = 1; i <= n; ++i)
{
++Si; //释放占用的资源;
for (在Si.queue中等待的每一个进程P){ //检查每种资源的等待队列的所有进程;
从等待队列Si.queue中取出进程P;
if (判断进程P是否通过Swait中的测试)
//注:与signal不同,这里要进行重新判断;
{//通过检查(资源够用)时的处理;
进程P进入就绪队列;
}
else
{//未通过检查(资源不够用)时的处理;
进程P进入某等待队列;
}
}
}
}
一般信号量集
一般信号量集是指同时需要多种资源、每种资源占用的数目不同、且可分配的资源还存在一个临界值时的信号量处理
一般信号量集的基本思路就是在AND型信号量集的基础上进行扩充,在一次原语操作中完成所有的资源申请
进程对信号量Si的
测试值为 ti (表示信号量的判断条件,要求Si≥ti,即当资源数量低于ti时,便不予分配)
占用值为di(表示资源的申请量,即Si=Si-di)
对应的P、V原语格式为:
Swait(S1,t1,d1;,,,;Sn,tn,dn);
Ssignal(s1,d1,;,,,;Sn,dn);
一般信号量可以用于各种情况的资源分配和释放,几种特殊情况:
Swait(S,d,d)表示每次申请d个资源,当少于d个时,便不分配)
Swait(S,1,1)表示互斥信号量
Swait(S,1,0)可作为一个可控开关(当S≥1时,允许多个进程进入临界区;当S=0时,禁止任何进程进入临界区)