经典的IPC问题 -- 哲学家就餐问题、读者-写者问题、理发师睡觉问题、生产者-消费者问题详解

一、哲学家就餐问题

这个问题可以简单的如下描述:

五个哲学家未做在一张圆桌周围,每个哲学家面前都有一盘通心面,由于通心面很滑,所以需要两把叉子才能夹住,相邻两个盘子之间放着一把叉子,如图所示

哲学家的生活中有两种交替的活动:吃饭和思考,当一个哲学家饿了的时候,他就试图分两次去取左边的和右边的叉子,每次那一把,但不分次序,如果成功地得到了两把叉子,他就开始吃饭,吃完后放下叉子继续思考,关键问题就是:如何为每个哲学家都写一段描述其行为的程序且绝不会死锁呢?


哈哈,其实这个问题如果换成5个在侃侃而谈的中国人,中国人吃饭只有两个状态:扯淡或吃饭,每碗饭旁边都有一枝筷子,两只筷子才能吃饭,所以问题就是怎么避免死锁


  • 解决方案

  • 简单的说,如果每个哲学家都拿起左面的叉子,那么谁都没法吃饭也没法思考,程序就进入了死锁

  • 但是我们可以这样改进:首先拿起左侧的叉子,如果右侧叉子可以用就拿起右侧的叉子吃饭,如果右侧的叉子不可以用就放下左侧的叉子给别人用。这个解法也是错误的,因为有一种可能,那就是某一时刻,所有的哲学家都同时拿起了左侧的叉子,发现右侧叉子不可用,于是放下左侧的叉子,过一会又同时拿起左侧的叉子,却依然发现右侧的叉子不可用,如此不断的循环往复,却无法取得任何进展,这样的情况就被称为“饥饿”

  • 进一步,如果每个哲学家发现右侧叉子不可用就放下左侧叉子,但是等待一个随机的时间然后再拿起左侧的叉子,由于等待时间是随机的,所以像上一个方案中那样陷入“饥饿”的可能就被大幅降低,在网络通信中,如果两台计算机同时发送包,那么每台计算机等待一段随即时间后再次尝试,这样的方案工作效果很好,但是在少数应用中,人们希望有一种始终都能工作的方案,而不是依靠不可靠的随机数字

  • 使用信号量、互斥量,使用一个二元信号量对哲学家除思考外的活动(拿左叉子、拿右叉子、进食、放下左叉子、放下右叉子)加以保护,在开始拿叉子前,哲学家先对互斥量mutex执行down操作,放回叉子后再对mutex执行up操作,但是这个方案使得每一时刻最多只能有一个哲学家在进餐,而事实上,五把叉子是可以允许同时两个哲学家进餐的

  • 如下解法中不仅不会出现死锁,而且对于任意位哲学家的情况都能获得最大的并行度,算法使用一个数组state很重每一个哲学家的状态(进餐、思考或是正在拿叉子),只有一个哲学家的两个邻居都在思考的时候他才可以拿叉子进餐,

该程序使用了一个信号量数组,每个信号量对应一个哲学家,这样在所需的叉子被占用时,想进餐的哲学家就被阻塞


二、读者-写者问题

哲学家就餐问题对于互斥访问有限资源的竞争问题(如IO设备)一类的建模过程十分有用,另一个注明的问题是读者-写者问题,他为数据库访问建立了一个模型,例如,一个飞机订票系统,其中有许多竞争的进程试图读写其中的数据,多个进程同时读数据库是可以接受的,但是如果一个进程正在更新数据库,则所有其他进程都不能访问该数据库,即使读操作也不可以

假设此时,有一个读者在访问数据库,这时,又一个读者到来,数据库程序将运行新来的进程访问数据库,但是如果这时到来的是一个写者,那么写者就会进入阻塞(被阻挡在外)以等待读操作完成,假设每个读操作的时间是5秒,但是每2秒有一个读者到来,那么由于上一个读者还在数据库中,所以写者不能进入,但是新的读者却可以进入,这样循环往复,写者将永远无法进入数据库


  • 解决方案

如果一个读者到来,发现有写者正在等待,那么他将不会进入数据库,而是等待在写者的后面,但是这样的效率很低

下面的写者优先解法很好的解决了这一问题


理发店里有一位理发师、一把理发椅和n把供等候理发的顾客坐的椅子。如果没有顾客,则理发师便在理发椅上睡觉,如图2-20所示。当一个顾客到来时,他必须先叫醒理发师,如果理发师正在理发时又有顾客来到,则如果有空椅子可坐,他们就坐下来等。如果没有空椅子,他就离开。这里的问题是为理发师和顾客各编写一段程序来描述他们的行为,要求不能带有竞争条件。

图 2-20.gif (7360 bytes)

图2-20 睡觉的理发师

我们的解法使用三个信号量:customers,用来记录等候理发的顾客数(不包括正在理发的顾客);barbers,记录正在等候顾客的理发师数,为0或1;mutex,用于互斥。我们还需要一个变量waiting,它也用于记录等候的顾客数,实际上是customers的一份拷贝。之所以使用waiting是因为无法读取信号量的当前值。在该解法中,进入理发店的顾客必须先看等候的顾客数,如果少于椅子数,他留下来等,否则他就离开。

我们的解法示于图2-21。

	# define CHAIRS 5 /*为等待的顾客准备的椅子数*/

	typedef int semaphone ; /*运用你的想象力*/
	semaphore customers=0; /*等待服务的顾客数*/
	semaphore barbers=0; /*等待顾客的理发师数*/
	semaphore mutex=1; /*用于互斥*/
	int waiting=0; /*等待的顾客(还没理发的)*/

	void barber(void)
	{
		while(TRUE)
		{
			down(customers); 
			/*如果顾客数是0,则睡眠*/
			down(mutex); /*要求进程等待*/
			waiting=waiting-1; /*等待顾客数减1*/
			up(barbers); 
			/*一个理发师现在开始理发了*/
			up(mutex); /*释放等待*/
			cut_hair(); /*理发(非临界区操作)*/
		}
	}

    void customers(void)
   {
       down(mutex);/*进入临界区*/
       if(waiting < CHAIRS)
       {/*如果没有空椅子,就离开*/
          waiting = waiting + 1;/*等待顾客数加1*/
          up(customers);  /*如果必要的话,唤醒理发师*/
          up(mutex); /*释放访问等待*/
          down(barbers);/*如果barbers为0,就入睡*/
          get_haircut();/*坐下等待服务*/
        }
        else
           up(mutex);/*店里人满了,走吧*/
    }
       

 

图2-21 理发师问题的一种解法、---

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

一、明确定义

要理解生产消费者问题,首先应弄清PV操作的含义PV操作是由P操作原语和V操作原语组成(原语是不可中断的过程),对信号量进行操作,具体定义如下:

    PS):①将信号量S的值减1,即S=S-1

           ②如果S³0,则该进程继续执行;否则该进程置为等待状态,排入等待队列。

    VS):①将信号量S的值加1,即S=S+1

           ②如果S>0,则该进程继续执行;否则释放队列中第一个等待信号量的进程。

这只是书本的定义,对于这部分内容,老师先不要急于解释上面的程序流程,而是应该让学生首先知道P操作与V操作到底有什么作用。

P操作相当于申请资源,而V操作相当于释放资源。所以要学生记住以下几个关键字:

P操作-----à申请资源

V操作----à释放资源

二、形象启发

为此举两个生活中的例子:

例一:在公共电话厅打电话

公共电话厅里有多个电话,如某人要打电话,首先要进行申请,看是否有电话空闲,若有,则可以使用电话,如果电话亭里所有电话都有人正在使用,那后来的人只有排队等候。当某人用完电话后,则有空电话腾出,正在排队的第一个人就可以使用电话。这就相当于PV操作:

某人要打电话,首先要进行申请,相当于执行一次P操作,申请一个可用资源(电话);

某人用完电话,则有空电话腾出,相当于执行一次V操作,释放一个可用资源(电话)。

在多媒体课件中,这部分内容充分通过动画效果,演示整个申请电话资源(P操作)与释放电话资源(V操作)的过程,同时显示当前可用的资源个数(电话个数)。课件直观生动,一目了然,学生非常容易接受,并且理解深刻。

三、分层解剖

在理解了PV操作的的含义后,就必须讲解利用PV操作可以实现进程的两种情况:互斥和同步。根据互斥和同步不同的特点,就有利用PV操作实现互斥与同步相对固定的结构模式。这里就不详细讲解了。但生产者-消费者问题是一个有代表性的进程同步问题,要学生透彻理解并不容易。但是如果我们将问题细分成三种情况进行讲解,理解难度将大大降低。

1)一个生产者,一个消费者,公用一个缓冲区。

可以作以下比喻:将一个生产者比喻为一个生产厂家,如伊利牛奶厂家,而一个消费者,比喻是学生小明,而一个缓冲区则比喻成一间好又多。第一种情况,可以理解成伊利牛奶生产厂家生产一盒牛奶,把它放在好又多一分店进行销售,而小明则可以从那里买到这盒牛奶。只有当厂家把牛奶放在商店里面后,小明才可以从商店里买到牛奶。所以很明显这是最简单的同步问题。

解题如下:

定义两个同步信号量:

empty——表示缓冲区是否为空,初值为1

full——表示缓冲区中是否为满,初值为0

生产者进程

while(TRUE){

生产一个产品;

     P(empty);

     产品送往Buffer;

     V(full);

}

消费者进程

while(TRUE){

P(full);

   Buffer取出一个产品;

   V(empty);

   消费该产品;

2)一个生产者,一个消费者,公用n个环形缓冲区。

    第二种情况可以理解为伊利牛奶生产厂家可以生产好多牛奶,并将它们放在多个好又多分店进行销售,而小明可以从任一间好又多分店中购买到牛奶。同样,只有当厂家把牛奶放在某一分店里,小明才可以从这间分店中买到牛奶。不同于第一种情况的是,第二种情况有N个分店(即N个缓冲区形成一个环形缓冲区),所以要利用指针,要求厂家必须按一定的顺序将商品依次放到每一个分店中。缓冲区的指向则通过模运算得到。

 

解题如下:

  定义两个同步信号量:

empty——表示缓冲区是否为空,初值为n

full——表示缓冲区中是否为满,初值为0

    设缓冲区的编号为1n-1,定义两个指针inout,分别是生产者进程和消费者进程使用的指针,指向下一个可用的缓冲区。

 

生产者进程

while(TRUE){

     生产一个产品;

     P(empty);

     产品送往bufferin);

     in=(in+1)mod n

     V(full);

     }

消费者进程

while(TRUE){

 P(full);

   bufferout)中取出产品;

   out=(out+1)mod n

   V(empty);

   消费该产品;

   }

3)一组生产者,一组消费者,公用n个环形缓冲区

第三种情况,可以理解成有多间牛奶生产厂家,如蒙牛,达能,光明等,消费者也不只小明一人,有许许多多消费者。不同的牛奶生产厂家生产的商品可以放在不同的好又多分店中销售,而不同的消费者可以去不同的分店中购买。当某一分店已放满某个厂家的商品时,下一个厂家只能把商品放在下一间分店。所以在这种情况中,生产者与消费者存在同步关系,而且各个生产者之间、各个消费者之间存在互斥关系,他们必须互斥地访问缓冲区。

解题如下:

定义四个信号量:

empty——表示缓冲区是否为空,初值为n

full——表示缓冲区中是否为满,初值为0

mutex1——生产者之间的互斥信号量,初值为1

mutex2——消费者之间的互斥信号量,初值为1

设缓冲区的编号为1n-1,定义两个指针inout,分别是生产者进程和消费者进程使用的指针,指向下一个可用的缓冲区。

 

生产者进程

while(TRUE){

     生产一个产品;

     P(empty);

     P(mutex1)

     产品送往bufferin);

     in=(in+1)mod n

     V(mutex1);

     V(full);

     }

消费者进程

while(TRUE){

 P(full);

   P(mutex2)

   bufferout)中取出产品;

   out=(out+1)mod n

   Vmutex2);

   V(empty);



评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值