经典IPC问题

哲学家进餐问题

有5个哲学家,围坐在圆桌旁,他们的生活方式是交替地进行思考和进餐;圆桌上间隔地摆放着5把叉子和5个装有通心粉的盘子,规定第i号哲学家固定坐在第i把椅子上( i=0,1,2,3,4),且每个哲学家必须两手分别拿起他左右两旁的那两把叉子,才能吃通心粉;假定通心粉的数量足够5个哲学吃的。

第一种实现:
利用管程机制实现(最终该实现是失败的,见以下分析):
原理:不是对每只筷子设置信号量,而是对每个哲学家设置信号量。test()函数有以下作用:
a. 如果当前处理的哲学家处于饥饿状态且两侧哲学家不在吃饭状态,则当前哲学家通过test()函数试图进入吃饭状态。
b. 如果通过test()进入吃饭状态不成功,那么当前哲学家就在该信号量阻塞等待,直到其他的哲学家进程通过test()将该哲学家的状态设置为EATING。
c. 当一个哲学家进程调用put_forks()放下筷子的时候,会通过test()测试它的邻居,如果邻居处于饥饿状态,且该邻居的邻居不在吃饭状态,则该邻居进入吃饭状态。
由上所述,该算法不会出现死锁,因为一个哲学家只有在两个邻座都不在进餐时,才允许转换到进餐状态。
该算法会出现某个哲学家适终无法吃饭的情况,即当该哲学家的左右两个哲学家交替处在吃饭的状态的时候,则该哲学家始终无法进入吃饭的状态,因此不满足题目的要求。
但是该算法能够实现对于任意多位哲学家的情况都能获得最大的并行度,因此具有重要的意义。

#define N        5
#define LEFT     (i+N-1)%N   //number of i's left neighbor
#define RIGHT    (i+1)%N
#define THINKING 0
#define HUNGRY   1   //philosophers is trying to get forks
#define EATING   2

typedef int semaphore;
int state[N];  //everyone's state
semaphore mutex=1;  //mutual exclusion for critical regions
semaphore s[N];   //one semaphore per philosopher

void philosopher(int i)
{
    while(TRUE)
    {
        think();
        take_forks(i);
        eat();
        put_forks();
    }
}

void take_forks(int i)
{
    down(&mutex);
    state[i]=HUNGRY;
    test(i);
    up(&mutex);
    down(&s[i]);  //block if forks are not acquired
}

void put_forks(i)
{
    down(&mutex);
    state[i]=THINKING;
    test(LEFT);
    test(RIGHT);
    up(&mutex);
}

void test(i)
{
    if(state[i]==HUNGRY&&state[LEFT]!=EATING&&state[RIGHT]!=EATING)
    {
        state[i]=EATING;
        up(&s[i]);
    }
}

第二种实现:
至多只允许四个哲学家同时进餐,以保证至少有一个哲学家能够进餐,最终总会释放出他所使用过的两支筷子,从而可使更多的哲学家进餐。以下将count 作为信号量,只允许4个哲学家同时进入餐厅就餐,这样就能保证至少有一个哲学家可以就餐,而申请进入餐厅的哲学家进入等待队列,根据FIFO 的原则,总会进入到餐厅就餐,因此不会出现饿死和死锁的现象。

struct semaphore chopstick[5]=(1,1,1,1,1);
struct semaphore count=4;

void philosopher(int i)
{
    while(TRUE)
    {
        think();
        P(count);
        P(chopstick[i]);
        P(chopstick[(i+1)%5]);
        eat();
        V(chopstick[i]);
        V(chopstick[(i+1)%5]);
        V(count);
    }
}

第三种实现
仅当哲学家的左右两支筷子都可用时,才允许他拿起筷子进餐。
方法1:利用AND 型信号量机制实现:根据课程讲述,在一个原语中,将一段代码同时需要的多个临界资源,要么全部分配给它,要么一个都不分配,因此不会出现死锁的情形。当某些资源不够时阻塞调用进程;由于等待队列的存在,使得对资源的请求满足FIFO 的要求,因此不会出现饥饿的情形。

semaphore chopstick[5]= {11111};
void philosopher(int I)
{
    while(true)
    {
        think();
        Swait(chopstick[(I+1)]%5,chopstick[I]);
        eat();
        Ssignal(chopstick[(I+1)]%5,chopstick[I]);
    }
}

方法2:利用信号量的保护机制实现。通过信号量mutex对eat()之前的取左侧和右侧筷子的操作进行保护,使之成为一个原子操作,这样可以防止死锁的出现。

semaphore mutex = 1 ;
semaphore chopstick[5]= {11111};
void philosopher(int i)
{
    while(true)
    {
        think();
        wait(mutex);
        wait(chopstick[(i+1)%5]);
        wait(chopstick[i]);
        signal(mutex);
        eat();
        signal(chopstick[(i+1)%5]);
        signal(chopstick[i]);
    }
}

第四种实现
规定奇数号的哲学家先拿起他左边的筷子,然后再去拿他右边的筷子;而偶数号的哲学家则相反.按此规定,将是1,2号哲学家竞争1号筷子,3,4号哲学家竞争3号筷子.即五个哲学家都竞争奇数号筷子,获得后,再去竞争偶数号筷子,最后总会有一个哲学家能获得两支筷子而进餐。而申请不到的哲学家进入阻塞等待队列,根FIFO原则,则先申请的哲学家会较先可以吃饭,因此不会出现饿死的哲学家。

semaphore chopstick[5]= {11111};
void philosopher(int i)
{
    while(true)
    {
        think();
        if(i%2 == 0) //偶数哲学家,先右后左。
        {
            wait(chopstick[(i+1)%5]) ;
            wait(chopstick[i]) ;
            eat();
            signal(chopstick[(i+1)%5]) ;
            signal(chopstick[i]) ;
        }
        else //奇数哲学家,先左后右。
        {
            wait(chopstick[i]) ;
            wait(chopstick[(i+1)%5]) ;
            eat();
            signal(chopstick[i]) ;
            signal(chopstick[(i+1)%5]) ;
        }
    }
}

读者-写者问题

访问规则:
1、多个读者可以同时读数据
2、任何时刻只能有一个写者写数据
3、一个读者与一个写者不能同时在相应的临界区中
实质: 一个写者不能与其它的读者或写者同时访问相
应的临界资源。

Semaphore mutex, wrt=1,1;
int readcount=0;
void Writer(void)
{
    Down(wrt);
    Perform writing;
    Up(wrt);
}
void Reader(void)
{
    Down(mutex);
    readcount=readcount+1;
    if(readcount == 1)
        Down(wrt);
    Up(mutex);
    Perform reading;
    Down(mutex);
    readcount=readcount-1;
    if(readcount == 0)
        Up(wrt);
    Up(mutex);
}

睡眠的理发师

1、5把供顾客等候的椅子
2、一个理发师同时只能给一个顾客理发
3、没有顾客,理发师进入睡眠状态。

#define CHAIRS 5
typedef int semaphore;
semaphore customers=0;
semaphore barbers=0;
semaphore mutex=1;  //protect 'waiting'
int waiting=0;

void barber(void)
{
    while(TRUE)
    {
        down(&customers);
        down(&mutex);
        waiting=waiting-1;
        up(&barbers);
        up(&mutex);
        cut_hair();
    }
}

void customer(void)
{
    down(&mutex);
    if(waiting<CHAIRS)
    {
        waiting=waiting+1;
        up(&customers);
        up(&mutex);
        down(&barbers);
        get_haircut();
    }
    else
        up(&mutex);
}

另一版:

一个理发店有一个入口和一个出口。理发店内有一个可站5 位顾客的站席区、4 个单人沙发、3 个理发师及其专用理发工具、一个收银台。新来的顾客坐在沙发上等待;没有空沙发时,可在站席区等待;站席区满时,只能在入口外等待。理发师可从事理发、收银和休息三种活动。理发店的活动满足下列条件:
1)休息的理发师是坐地自己专用的理发椅上,不会占用顾客的沙发;
2)处理休息状态的理发师可为在沙发上等待时间最长的顾客理发;
3)理发时间长短由理发师决定;
4)在站席区等待时间最长的顾客可坐到空闲的理发上;
5)任何时刻最多只能有一个理发师在收银。
试用信号量机制或管程机制实现理发师进程和顾客进程。

原理:
(1)customer 进程:
首先检查站席区是否已满(stand_capacity),若满选择离开,否则进入站席区,即进入理发店。在站席区等待沙发的空位(信号量sofa),如果沙发已满,则进入阻塞等待队列,直到出现空位,在站席区中等待时间最长的顾客离开站席区(stand_capacity)。坐到沙发上,等待理发椅(barber_chair),如果理发椅已满,则进入阻塞等待队列,直到出现空位,在沙发上等待时间最长的顾客离开沙发(释放信号量sofa)。坐到理发椅上,释放准备好的信号(customer_ready),获得该理发师的编号(0~1 的数字)。等待理发师理发结束(finished[barber_number])。在离开理发椅之前付款(payment),等待收据 (receipt),离开理发椅(leave_barberchair)。最后离开理发店。
这里需要注意几点:
a) 首先是几个需要进行互斥处理的地方,主要包括:进入站席区、进入沙发、进入理发椅和付款几个地方。
b) 通过barber_chair 保证一个理发椅上最多只有一名顾客。但这也不够,因为单凭baber_chair 无法保证一名顾客离开理发椅之前,另一位顾客不会坐到该理发椅上,因此增加信号量leave_barberchair,让顾客离开理发椅后,释放该信号,而理发师接收到该信号后才释放barber_chair 等待下一位顾客。
c) 在理发的过程中,需要保证是自己理发完毕,才能够进行下面的付款、离开理发椅的活动。这个机制是通过customer 进程获得给他理发的理发师编号来实现的,这样,当该编号的理发师释放对应的finished[i]信号的时候,该顾客才理发完毕。
d) 理发师是通过mutex 信号量保证他们每个人同时只进行一项操作(理发或者收款)。
e) 为了保证该顾客理发完毕后马上可以付款离开,就应该保证给该顾客理发的理发师在理发完毕后马上到收银台进入收款操作而不是给下一位顾客服务。在伪码中由以下机制实现:即顾客在释放离开理发椅的信号前,发出付款的信号。这样该理发师得不到顾客的离开理发椅的信号,不能进入下一个循环为下一名顾客服务,而只能进入收款台的收款操作。直到顾客接到收据后,才释放离开理发椅的信号,离开理发椅,让理发师释放该理发椅的信号,让下一位等待的顾客坐到理发椅上。
(2)barber 进程
首先将该理发师的编号压入队列,供顾客提取。等待顾客坐到理发椅坐好(信号量customer_ready),开始理发,理发结束后释放结束信号(finished[i])。等待顾客离开理发椅(leave_barberchair)(期间去收银台进行收款活动),释放理发椅空闲信号(barber_chair),等待下一位顾客坐上来。
(3)cash(收银台)进程
等待顾客付款(payment),执行收款操作,收款操作结束,给付收据(receipt)。

信号量总表:
stand_capacity 顾客等待进入理发店 顾客离开站席区
sofa 顾客等待坐到沙发 顾客离开沙发
barber_chair 顾客等待空理发椅,理发师释放空理发椅
customer_ready 理发师等待,直到一个顾客坐到理发椅;顾客坐到理发椅上,给理发师发出信号
mutex 等待理发师空闲,执行理发或收款操作;理发师执行理发或收款结束,进入空闲状态
mutex1 执行入队或出队等待入队或出队结束,释放信号
finished[i] 顾客等待对应编号理发师理发结束; 理发师理发结束,释放信号
leave_barberchair 理发师等待顾客离开理发椅;顾客付款完毕得到收据,离开理发椅释放信号
payment 收银员等待顾客付款顾客付款,发出信号
receipt 顾客等待收银员收、开具收据;收银员收款结束、开具收据,释放信号

semaphore stand_capacity=5;
semaphore sofa=4;
semaphore barber_chair=3;
semaphore customer_ready=0;
semaphore mutex=3;
semaphore mutex1=1;
semaphore finished[3]= {0,0,0};
semaphore leave_barberchair=0;
semaphore payment=0;
semaphore receipt=0;
void customer()
{
    int barber_number;
    wait(stand_capacity); //等待进入理发店
    enter_room(); //进入理发店
    wait(sofa); //等待沙发
    leave_stand_section(); //离开站席区
    signal(stand_capacity);
    sit_on_sofa(); //坐在沙发上
    wait(barber_chair); //等待理发椅
    get_up_sofa(); //离开沙发
    signal(sofa);
    wait(mutex1);
    sit_on_barberchair(); //坐到理发椅上
    signal(customer_ready);
    barber_number=dequeue(); //得到理发师编号
    signal(mutex1);
    wait(finished[barber_number]); //等待理发结束
    pay(); //付款
    signal(payment); //付款
    wait(receipt); //等待收据
    get_up_barberchair(); //离开理发椅
    signal(leave_barberchair); //发出离开理发椅信号
    exit_shop(); //了离开理发店
}
void barber(int i)
{
    while(true)
    {
        wait(mutex1);
        enqueue(i); //将该理发师的编号加入队列
        signal(mutex1);
        wait(customer_ready); //等待顾客准备好
        wait(mutex);
        cut_hair(); //理发
        signal(mutex);
        signal(finished[i]); //理发结束
        wait(leave_barberchair); //等待顾客离开理发椅信号
        signal(barber_chair); //释放barber_chair 信号
    }
}
void cash() //收银
{
    while(true)
    {
        wait(payment); //等待顾客付款
        wait(mutex); //原子操作
        get_pay(); //接受付款
        give_receipt(); //给顾客收据
        signal(mutex);
        signal(receipt); //收银完毕,释放信号
    }
}

分析:
在分析该问题过程中,出现若干问题,是参阅相关资料后才认识到这些问题的隐蔽性和严重性的,主要包括:
(1)在顾客进程,如果是在释放leave_barberchair 信号之后进行付款动作的话,很容易造成没有收银员为其收款的情形, 原因是: 为该顾客理发的理发师收到leave_barberchair 信号后,释放barber_chair 信号,另外一名顾客坐到理发椅上,该理发师有可能为这另外一名顾客理发,而没有为刚理完发的顾客收款。为解决这个问题,就是采取在释放leave_barberchair 信号之前,完成付款操作。这样该理发师无法进入下一轮循环为另外顾客服务,只能到收银台收款。
(2)本算法是通过给理发师编号的方式,当顾客坐到某理发椅上也同时获得理发师的编号,如此,当该理发师理发结束,释放信号,顾客只有接收到为其理发的理发师的理发结束信号才会进行付款等操作。这样实现,是为避免这样的错误,即:如果仅用一个finished 信号量的话,很容易出现别的理发师理发完毕释放了finished 信号,把正在理发的这位顾客赶去付款,而已经理完发的顾客却被阻塞在理发椅上的情形。当然也可以为顾客进行编号,让理发师获取他理发的顾客的编号,但这样就会限制顾客的数量,因为finished[]数组不能是无限的。而为理发师编号,则只需要三个元素即可。

练习题:

某小型超级市场,可容纳50人同时购物。入口处有篮子,每个购物者可拿一只篮子入内购物。出口处结帐,并归还篮子(出、入口禁止多人同时通过)。试用信号量和P、 V操作写出购物者的同步算法。

struct semaphore;
semaphore mutex1=1,mutex2=1,empty=50;

cobegin
void customer(void)
{
    P(empty);
    P(mutex1);
    take a basket;
    V(mutex1);
    shopping;
    P(mutex2);
    put back a basket;
    V(mutex2);
    leaving;
    V(empty);
}
conend;

桌上有个只能盛得下一个水果的空盘子。爸爸可向盘中放苹果或桔子,儿子专等吃盘中的桔子,女儿专等吃盘中的苹果。规定:当盘子空时,一次只能放入一个水果供吃者取用。试用信号量和P、 V操作实现爸爸、儿子和女儿这三个循环进程之间的同步。

#define ORE 0
#define APP 1
struct semaphore;
semaphore empty=1,orange=0,apple=0;
int fruit;

cobegin
void father(void)
{
    while(TRUE)
    {
        P(empty);
        fruit=put_fruit();
        if(fruit==ORE) V(orange);
        else V(apple);
    }
}

void son(void)
{
    while(TRUE)
    {
        P(orange);
        eat orange;
        V(empty);
    }
}

void daughter(void)
{
    while(TRUE)
    {
        P(apple);
        eat apple;
        V(empty);
    }
}
coend;

设A、 B两点之间是一段东西向的单行车道,现在要设计一个AB路段自动管理系统,管理规则如下:当AB间有车辆在行驶时同方向的车可以同时驶入AB段,但另一方向的车必须在AB段外等待;当AB段之间无车辆行驶时,到达AB段的任一方向的车都可进入AB段,但不能从两个方向同时驶入,即只能有一个方向的车驶入;当某方向在AB段行驶的车辆驶出了AB段且暂无车辆进入AB段时,应让另一方向等待的车辆进入AB段行驶。试用信号量和P、V操作管理AB路段车辆的行驶。

struct semaphore;
semaphore mutex=1,mAtoB=1,mBtoA=1;
int car_numAtoB=0,car_numBtoA=0;

cobegin
void carFromAtoB(void)
{
    P(mAtoB);
    if(car_numAtoB==0)
        P(mutex);
    ++car_numAtoB;
    V(mAtoB);
    Car runs from A to B;
    P(mAtoB);
    --car_numAtoB;
    if(car_numAtoB==0)
        V(mutex);
    V(mAtoB);
}
void carFromBtoA(void)
{
    P(mBtoA);
    if(car_numBtoA==0)
        P(mutex);
    ++car_numBtoA;
    V(mBtoA);
    Car runs from B to A;
    P(mBtoA);
    --car_numBtoA;
    if(car_numBtoA==0)
        V(mutex);    
    V(mBtoA);
}
coend;

有两个用户进程A和B,在运行过程中都要使用系统中的一台打印机输出计算结果。为保证这两个进程能正确地打印出各自的结果,请用信号量和P、 V操作写出各自的有关申请、使用打印机的代码。

struct semaphore;
semaphore mutex=1;

cobegin
void process(void)
{
    while(TRUE)
    {
        P(mutex);
        print();
        V(mutex);
    }
}
coend;

有一个阅览室,共有100个座位,读者进入时必须先在一张登记表上登记,该表为每一座位列一表目,包括座号和读者姓名等,读者离开时要消掉登记的信息,试用PV操作描述读者进程之间的同步关系。

struct semaphore;
semaphore mutex=1,seats=100;

cobegin
void get_in(void)
{
    P(seats);
    P(mutex);
    登记;
    V(mutex);
    read();
}
void get_out(void)
{
    V(seats);
    P(mutex);
    消去登记;
    V(mutex);
}
coend;

设有一台计算机,有两条I/O通道,分别挂一台输入机和一台打印机。若要把输入机上的数据逐一地输入到缓冲区B1中,然后处理,并把结果搬到缓冲区B2中,最后在打印机上输出。用P-V操作写出这些进程的同步算法。
这里写图片描述

struct semaphore empty1, full1, empty2, full2=1, 0, 1, 0;
data B1, B2;

cobegin
void IP(void)
{
    data x;
    while(TRUE)
    {
        Get a new  data from input unit into x;
        P(empty1);
        B1=x;
        V(full1);
    }
}
void PP(void)
{
    data x;
    while(TRUE)
    {
        P(full2);
        x=B2;
        V(empty2);
        print x;
    }
}
void CP(void)
{
    data x,y;
    while(TRUE)
    {
        P(full1);
        x=B1;
        V(empty1);
        Compute on x and put the result into y;
        P(empty2);
        B2=y;
        V(full2);
    }
}
coend;

假定有三个进程R、W1、W2共享一个缓冲区B,B中每次只能存放一个整数。进程R每次启动输入设备读一个整数且把它存放在缓冲区B中,若存放到缓冲区B中的是奇数,则由进程W1将其取出打印,否则由进程W2将其取出打印。要求用PV操作管理这3个并发进程,使它们能够正确同步工作。

struct semaphore empty, fullo, fulle=1, 0, 0;
int B;
void process R(void)
{
    int x;
    while(TRUE)
    {
        input data into x;
        P(empty);
        B=x;
        if(x%2==1)  V(fullo);
        else  V(fulle);
    }
}
void process W1(void)
{
    int y;
    while(TRUE)
    {
        P(fullo);
        y=B;
        V(empty);
        print y;
    }
}
void process W2(void)
{
    int z;
    while(TRUE)
    {
        P(fulle);
        z=B;
        V(empty);
        print z;
    }
}
  • 5
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值