您的位置:首页 > 其它

如何使用信号量处理问题

2016-11-13 19:03 323 查看
从信号量的理论跨越到对其应用解决实际的问题,对于我这种人来说,是一种脱离地心引力飞向太阳系的体验。

以下有7个问题(不写目录了)。

1、

生产者---消费者问题

有一个或多个生产者生产某种类型的数据(记录、字符),并防止在缓冲区中;有一个消费者从缓冲区中取数据,每次取一项;系统保证避免对缓冲区的重复操作,也就是说,在任何时候只有一个主体(生产者或者消费者)可以访问缓冲区。问题是要确保这种情况,当缓冲区已满时,生产者不会继续向其中添加数据;当缓冲区为空时,消费者不会从中移走数据。

1)问题分析:

从题中可以发现需要对缓冲区进行同步互斥操作,生产者为消费者生产必要产品放入缓冲区,消费者为生产者生产空闲缓冲区,这就需要同步操作;对于同一个缓冲区而言,只能有一个生产者或消费者访问,也就是生产者与生产者之间、生产者与消费者、消费者与消费者需要互斥操作。

对于生产者:

判断缓冲区是否为满,如果为满,则等待;否则,允许一个生产者写入。

对于消费者:

判断缓冲区是否为空,如果为空,则等待;否则,允许一个消费者读取。

2)伪码描述:

semaphore buffer, empty, full;//分别表示缓冲,缓冲剩余资源,缓冲已用资源的信号量
buffer.v = 1;//表示缓冲空闲
empty.v = MAX_SIZE;//表示缓冲为空
full.v = 0;//表示缓冲为空
void Producer()
{
while(isProduce()) {
//produce
P(empty);//判断是否有空闲的缓冲
P(buffer);//判断缓冲是否占用
append();//将产品放入缓冲中
V(buffer);//释放对缓冲的使用
V(full);//缓冲中添加一块产品
}
}
void Consumer()
{
while(isConsume()) {
P(full);//判断是否有非空的缓冲
P(buffer);//判断缓冲是否占用
take();//取走产品
V(buffer);//释放对缓冲的使用
V(empty);//多出一块空闲缓冲区
//consume
}
}


2、

哲学家进餐问题

有5位哲学家住在一座房子里,在他们面前有一张餐桌。每位哲学家的生活就是思考和吃饭。所有的哲学家的食物是意大利面条,由于缺乏手工技能,每位哲学家需要两把叉子进餐。一个圆盘上有一大碗面,每位哲学家面前有一个盘子,左手边有一个叉子。每个想吃饭的哲学家将坐到桌子旁分配给他的位置上,使用盘子两侧的叉子,取面和吃面。

1)问题分析:

题中的叉子显然是一个互斥资源,对于一个叉子而言,如果有一个哲学家占用,那么另一个哲学家只能等待。

对于每一个哲学家来说,

他需要判断能否申请到手边的两个叉子,如果只能得到一个或者没有得到叉子,只能等待;否则,可以进餐。

考虑到会出现饥饿的现象,比如:同时拿起左手边的叉子,所有哲学家同时等待。

为了解决这个问题,可以使用一个信号量表示餐桌上最多坐四人,第5位哲学家想坐时,只能等待。

2)伪码描述:

semaphore forkLeft, forkRight, people;//左手边的叉子,右手边的叉子,允许使用桌子的人数
forkLeft.v = forkRight.v = 1;//表示两手边的叉子都处于空闲
people.v = 4;//允许桌子的人数
void philosopherX()
{
P(people);//第X位哲学家申请使用桌子,桌边允许人数减少一个人
P(forkRight);//申请使用右手边的叉子
P(forkLeft);//申请使用左手边的叉子
//eat
V(forkRight);//释放两手的叉子
V(forkLeft);
V(people);//离开桌子,桌边允许人数加一
}


3、

理发师睡眠问题

理发师店里有一位理发师、一把理发椅和N把在等候区的椅子、M个希望理发的顾客。没有客人,理发师便在理发椅上睡觉。但一个顾客到来时,必须叫醒理发师或者等待正在理发的理发师。需要等待时如果等待区的椅子可坐,就坐下等待;否则离开理发店。

1)问题分析:

此处的理发椅,需要对其进行互斥操作,保证理发师和顾客之间,顾客和顾客之间只有一人使用,其中理发师与顾客之间需要处理同步问题,顾客与顾客之间需要处理互斥问题。

同时对于等候区的N把椅子,对于顾客来说也是竞争资源,需要信号量处理。但是考虑到坐不下就需要离开理发店,发现单纯使用信号量并不能很好的控制,需要一个计数器,来表示剩余多少座位可以坐下,如果不能坐,则离开,该计数器有互斥信号量控制。

对于理发师,如果没有理发,并且没有人需要理发,则在理发椅上睡觉,否则会去理发。同时需要记录等待区流动的人数。

对于顾客,先判断是否有位子坐,如果有位子,在判断是否能轮到他理发,如果轮到他理发,那么理发。同样需要记录等待区流动的人数。

2)伪码描述:

semaphore workingBarber,  waitingClient, waitingRoom;//表示理发师,顾客,等待区的信号量
int emptySeats = N;//等待区空位数
workingBarber.v = waitingClient.v = 0;//理发师并不工作,无顾客等待
waitingRoom.v = 1;//是否可以进入理发店
void Barber()
{
while(!isGoHome()) {//理发师是否下班
P(waitingClient);//顾客唤醒理发师
P(waitingRoom);//对等待区操作,空位子增加一个
emptySeats += 1;
V(workingBarber);//理发师工作
V(waitingRoom);//撤销对等待区操作
//cut hair
}
}
void Client()
{
P(waitingRoom);//对等待区操作
if(emptySeats > 0) {//判断是否有空位
emptySeats -= 1;
V(waitingClient);//顾客在等待
V(waitingRoom);//撤销对等待区操作
P(workingBarber);//理发师为顾客工作(同样,P操作与上一个V操作不能交换,理由见下)
//have haircut
}
else {
V(waitingRoom);//没有空位,离开
//leave
}
}
(子程序中原子操作不能交换的原因:当前操作,能保证在理发师和其他顾客对理发店的访问;互换之后,可能会发生理发师不能询问等待区,顾客不能进入理发店。因此,不交换更合适。)

4、

读者---写者问题

有一个多个进程共享的数据区,这个数据区可以是一个文件或者一块内存空间,甚至可以使一组寄存器。有一些进程(reader)只能读取这个数据区中的数据,一些进程(writer)只能往数据区中写数据;此外还必须满足一下条件:

a)任意多的读进程可以同时读这个文件。

b)一次只有一个写进程可以写文件。

c)如果一个写进程正在写文件,禁止任何读进程读文件。

1)问题分析:

对于共享的数据区,要满足条件a),需要对读进程与读进程之间进行同步操作;要满足b)和c)需要对写进程与读进程之间,写进程与写进程之间进行互斥操作。

对于读进程,需要先判断是否有写进程,若有,则等待;否则,访问数据区;

对于写进程,需要同样判断是否有写进程且读进程,若有,则等待;否则,访问数据区。

用标识变量isWrite标识是否写进程运行,用numberOfReader计数器记录有几个读进程运行。通过两个互斥量write和read分别控制对两变量的操作。

2)伪码描述:

以下是自己写的版本(如果有错欢迎指正),如果希望的到可信度较高的版本,请移步此处

semaphore write, read;//用于对变量的互斥控制
bool isWrite = false;//无写进程
int numberOfReader = 0;//无读进程
write.v = read.v = 1;//允许对变量修改
void Writer()
{
P(write);//申请对isWrite变量操作,如果有写进程则等待
P(read);//申请对numberOfReader变量操作,防止Reader对numberOfReader的改变,即避免出现写进程之后有读进程的情况
if(numberOfReader == 0) {//当无读进程时,运行写程序;否则,释放对numberOfReader变量的控制并离开
isWrite = true;//标记为写
//write
isWrite = false;//将标记撤销
V(read);//释放对numberOfReader变量操作
} else {
V(read);
}
V(write);//停止对isWrite变量的操作
}
void Reader()
{
P(write);//申请对isWrite变量的操作,防止isWrite的改变,即避免出现读进程之后有写进程的情况
if(!isWrite) {//判断是否有写进程,否则释放对isWrite的操作并离开
P(read);//申请对计数器的操作
numberOfReader++;//读进程增加一个
V(read);//释放操作
V(write);//释放对isWrite变量的操作,是其他读进程进入,同时确保读写之间只有一个进程。
//read
P(read);
numberOfReader--;
V(read);//
} else {
V(write);
}
}
5、[/b]

一带闸门的运河,其上又两架吊桥。吊桥坐落在一条公路上,为使该公路避开一块沼泽地而令其横跨运河两次。运河和公路的交通都是单方向的。运河上的基本运输由驳船担负。在一驳船接近吊桥A时就拉汽笛警告,若桥上无车辆,吊桥就吊起,直到驳船尾部通过此桥为止。对吊桥B也按同样次序处理。如何利用信号量的P、V操作,实现车辆和驳船的同步。

1)问题分析:

问题中有两个明显的互斥资源,吊桥A和吊桥B,因此需要两个信号量,来表示吊桥A、B的使用;有两个信号量的使用,需要考虑资源分配的有序性,这里将A的优先级设置的更高些。

对驳船进行分析,

驳船先判断吊桥A是否被使用,若无占用,则申请;之后并判断吊桥B的情况,无占用,则申请。A、B有一个占用,则等待。

当驳船申请A、B两处资源后,则通过吊桥A后,释放吊桥A,之后通过吊桥B,然后释放吊桥B。

对汽车进行分析,

由于汽车会出现多辆的情况,为了防止死锁,需要控制车辆的数量,也就是设置一个整形变量count用来记录车辆数量。防止多辆车辆对共享变量的更改,需要设置第三个信号量,保证对count的互斥操作。

对于某辆车,先申请资源A,如果资源A空闲,那么申请;如果资源B空闲,那么申请。只要A、B有一个占用,则等待。

当车辆申请A、B两处资源之后,则判断当前AB段公路上是否只有这一辆车,那么车辆通过B,释放B,之后通过A,释放A;如果有多辆车,则该车等待。

2)伪码描述:

semaphore mutexA, mutexB, mutexX;//定义对A,B,count的互斥控制
int count = 0;//记录当前车辆数量
mutexA.c = 1, mutexB.c = 1, mutexX.c = 1;//设定初始值,表示资源可用
void Car(...) {
P(mutexX);//申请对count的使用
count++;//车辆数加一
if(count == 1) {//如果当前只有一辆车,则进入,申请A,B资源
P(mutexA);
P(mutexB);
}
V(mutexX);//释放对count的使用
/*pass B, then pass A*/
P(mutexX);
count--;
if(count == 0) {//判断当前车辆是否驶离AB公路,驶出,则释放A,B资源
V(mutexB);
V(mutexA);
}
V(mutexX);
}
void Ship(...) {
P(mutexA);
P(mutexB);
/*pass A, then pass B*/
V(mutexA);
V(mutexB);
}


6、

下图数描述交通死锁的例子(设各方向上的汽车都是单线、直线行驶),若用计算机实现交通自动管理,请用信号量的P、V操作来实现各方向上汽车行驶的同步。

1)问题分析:

对于每一个路口,显然是一个互斥的资源,此处就需要四个信号量来表示。两个路口之间,需要记录通过的车辆数,也就是需要四个记录车辆数的计数器,为了保证每个计数器的正常工作,则需要配有四个信号量来保证对四个计数器的同步互斥操作。

按照上北下南,左西右东的规则,对于自北向南的车辆:

需要同时申请两个路口的通行并且只用一辆车时,才可通过;否则都处于等待状态。

其他三个方向的车辆也需要同样遵守上述的规定。

对于西北的路口,记为C1,顺时针方向依次标记各个路口;自北向南的车道,记为S1,同样顺时针方向标记各个车道。

2)伪码描述:

semaphore C[4], S[4], usedStreet;//表示路口,车道,使用车道的信号量
C[1].v = C[2].v = C[3].v = C[4].v = 1;//表示路口可用
S[1].v = S[2].v = S[3].v = S[4].v = 1;//表示车道可用
usedStreet.v = 3;
void Car(int id)
{
P(usedStreet);//判断是否有剩余可用的车道
P(S[id]);//申请车道,如果车道空闲,则准备驶入
P(C[id]);//申请通过C[id]路口
P(C[(id + 1) % 4]);//申请通过C[(id + 1) % 4]路口
//pass C[id], then pass C[(id + 1) % 4];//如果两个路口均空闲,则通过
V(C[id]);//通过路口C[id]
V(C[(id + 1) % 4]);//通过路口C[(id + 1) % 4]
V(S[id]);//通过这条车道
V(usedStreet);//释放对车道的使用,表示通过
}


7、

桌上有一空盘,允许存放一只水果。爸爸可向盘中放苹果,也可向盘中放桔子,儿子专等吃盘中的苹果,女儿专等盘中的桔子。规定当盘中空时,一次只能放一只水果供吃者取用,请问用信号量的P、V操作如何实现爸爸,儿子,女儿之间的同步问题。

1)问题分析:

盘子,是父子和父女之间都需要访问的资源,需要用信号量表示;苹果,是父子之间都处理的信息,需要用信号量表示;桔子,自然也需要用信号量来表示。

对于爸爸的行为:

需要盘子是否占用,

    如果没有占用,则判断苹果或桔子是否占用(即是否分发),

        若没有,放苹果或桔子,否则,什么都不做

    否则什么都不做。

对于儿子的行为:

判断盘子里是否有苹果,

       若是,则吃掉,否则什么都不做

对于女儿的行为:

判断盘子是否有桔子,

       若是,则吃掉,否则什么都不做

2)伪码描述:

semaphore dish, apple, orange;//定义信号量,表示盘子,苹果,桔子三类资源。
dish.v = 1, apple.v = 0, orange.v = 0;//分别表示盘子为空,苹果未放,桔子未放
void father(...) {
P(dish);//如果盘子为空,则放水果
V(apple);//如果苹果未放,则放苹果

P(dish);//如果盘子为空,则放水果
V(orange);//如果桔子未放,则放桔子
}
void son(...) {
P(apple);//如果是苹果,则取苹果
V(dish);//将盘子设为空
}
void daughter(...) {
P(orange);//如果是桔子,则取桔子
V(dish);//将盘子设为空
}

小结:

(我的一些浅显的见解,大神如果有好的想法,求分享呀)

解决实际问题

首先,需要对信号量操作有够深的理解,

1)信号量的赋值是为了,描述资源的使用情况;

2)对信号量的P()操作,其意义是:减一操作后,信号量的值为正,则执行该进程;否则阻塞;

3)对信号量的V()操作,其意义是:增一操作后,信号量的值为小于等于零,则从阻塞队列中取出阻塞进程并执行;否则什么也不做。

其次,我们需要对问题本身,进行分析。该过程中需要完成确定:

1)互斥量(通过二元信号量解决,还是计数信号量解决,或者两者共用的方式解决),

2)进程与进程之间的关系(同步还是互斥,比如生产者、消费者之间的关系),

3)确定资源的申请顺序(防止出现死锁,饥饿现象,比如哲学家进餐)。

等等

最后,对照实际问题对信号量赋予实际的含义,并通过P(),V()操作模拟问题本身。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: