1. 程式人生 > >PV操作經典問題

PV操作經典問題

一、5位哲學家就餐問題

semaphore fork[5];
for (int i=0;i<5;i++)
fork[i]=1;
cobegin
process philosopher_i( ) {   //i= 0,1,2,3,4
  while(true) {
    think( );
    P(fork[i]);			//先取右手的叉子
    P(fork[(i+1)%5]);		//再取左手的叉子
        eat( );
    V(fork[i]);
    V(fork[(i+1)%5]);
     }
}
coend

這個解法會出現死鎖!

為了避免死鎖

1.至多允許四個哲學家同時取叉子

2.奇數號先取左手邊的叉子,偶數號先取右手邊的叉子

3.每個哲學家取到手邊的兩把叉子才吃,否則一把叉子也不取

semaphore fork[5];
    for (int i=0;i<5;i++)
            fork[i]= 1;
    semaphore room=4;  //增加一個侍者
cobegin
process philosopher_i( ){/*i=0,1,2,3 */
	while(true) {
	think( );
	P(room);  //控制最多允許4為哲學家取叉子
	P(fork[i];
	P(fork[(i+1)%5] ) ; 
	   eat( );
	V(fork[i]);
	V(fork([i+ 1] % 5);
	V(room); 
     }
  }
coend
void philosopher (int i) 
{	if  i  mod 2==0 then 
	{ 
		P(fork[i]); 	//偶數哲學家先右手
		P(fork[(i+1) mod 5 ]);  //後左手
			eat();
		V(fork[i]);
		V (fork[(i+1) mod 5]);
	}
	else 
	 {
		P (fork[(i+1) mod 5 ]); //奇數哲學家,先左手
		P (fork[i]);   //後右手
			eat();
		V(fork[(i+1) mod 5]);
		V(fork[i]);
	}
}
semaphore fork[5];
    for (int i=0;i<5;i++)
            fork[i]= 1;
cobegin
process philosopher_i( ){/*i=0,1,2,3 */
	while(true) {
	think( );
	P(fork[i];//先取右手的叉子    /*i=4,P(fork[0])*/
	P(fork[(i+1)%5] ) ; //再取左手的叉子 /*i=4,P(fork[4])*/
	   eat( );
	V(fork[i]);
	V(fork([i+ 1] % 5);
     }
  }
coend

通過Hoare管程求解哲學家問題


詳解:

type dining_philosophers=monitor

 

int self_count[5];

InterfaceModule IM;

for (int i=0;i<5;i++)      //初始化,i為程序號

    state[i]=thinking;

define pickup,putdown;

use enter,leave,wait,signal;

void pickup(int i) {        //i=0,1,...,4

    enter(IM);

state[i]=hungry;

test(i);

if(state[i]!=eating)

wait(self[i],self_count[i],IM);

    leave(IM);

}

void putdown(int i) {       //i=0,1,2,..,4

   enter(IM);

state[i]=thinking;

test((i-1)%5);

test((i+1)%5);

   leave(IM);

}

void test(int k)  {       //k=0,1,...,4

if((state[(k-1)%5]!=eating)&&(state[k]==hungry)

&&(state[(k+1)%5]!=eating)) {

state[k]=eating;

signal(self[k],self_count[k],IM);

}

}

}

任一個哲學家想吃通心麵時呼叫過程pickup,吃完通心麵之後呼叫過程putdown。

cobegin              

process philosopher_i( ) {  //i=0,…,4

   while(true) {

    thinking( );

dining_philosophers.pickup(i);        

 eating( );

dining_philosophers.putdown(i);

      }

 }

coend


(A) 假設一開始3號哲學家,第一個做pickup,他會很順暢,在pickup中通過enter(IM)進入管程,修改自身狀態state[3]=hungry, 接著test(3)if條件通過,修改自身狀態state[3]=eating, 因為這時候沒有程序(哲學家)阻塞在self[3]上,即self_count[3]==0,對照signal(self[3], self_count[3], IM)中的PV實現,實際上此時signal什麼也不做。然後,判斷if (state[3]!=eating)不成立,因此wait操作也不做。再然後,3號哲學家執行leave(IM)退出管程,以便讓其他哲學家進入管程。
分析:實際上這裡3號哲學家成功的取到他需要的兩個叉子。他通過將自己的狀態改為eating,對於i=2號哲學家在取叉子的時需要做test(i)state[i+1]!=eating不成立;對於i=4號哲學家在取叉子的時需要做test(i)state[i-1]!=eating不成立;從而封堵其左右哲學家轉入eating狀態,24號哲學家不能成功轉入eating狀態,將執行wait操作,也即在3號哲學家沒有執行putdown中的test((i-1)%5)test((i+1)%5)之前,左右哲學家不能成功取到叉子。

(B) 接著,在3號哲學家還沒有putdown之前,如果1號哲學家執行pickup,他也會很順暢,與之前的3號哲學家pickup時的情形類似,pickup中通過enter(IM)進入管程,修改自身狀態state[1]=hungry, 接著test(1)if條件通過,修改自身狀態state[1]=eating, 因為這時候沒有程序(哲學家)阻塞在self[1]上,即self_count[1]==0,對照signal(self[1], self_count[1], IM)中的PV實現,實際上此時signal什麼也不做。然後,判斷if (state[1]!=eating)不成立,因此wait操作也不做。再然後,3號哲學家執行leave(IM)退出管程,以便讓其他哲學家進入管程。
分析:實際上這裡1號哲學家成功的取到他需要的兩個叉子。他通過將自己的狀態改為eating,從而封堵其左右哲學家轉入eating狀態,02號哲學家不能成功轉入eating狀態,也即在1號哲學家沒有執行putdown中的test((i-1)%5)test((i+1)%5)之前,不能成功取到叉子。

(C) 接著,在13號哲學家沒有putdown之前,此時,state[1]==eatingstate[3]==eating。如果2號哲學家執行pickup,他會很不順暢,pickup中通過enter(IM)進入管程,修改自身狀態state[2]=hungry,接著test(2)中的if條件state[1]!=eatingstate[3]!=eating都不成立,因此2號哲學家沒有成功把自身狀態修改為eating,也不用做test(2)signal(self[2], self_count[2], IM)操作,再接著判斷if (state[2]!=eating)成立,因此做wait(self[2], self_count[2], IM)操作,對照wait操作的PV實現,此時,self[2]_count++表示在self[2]等待的程序數加1,然後判斷if (IM.next_count>0),如果此時13號程序都沒有執行putdown中的signal操作,那麼該條件不成立,然後執行V(IM.mutex)退出管程,接著P(self[2])阻塞自己,等待13號哲學家執行putdown中的signal操作喚醒之。

(D) 接著,假設1號哲學家吃完,執行putdown,在putdown中通過enter(IM)進入管程,並修改自身狀態state[1]=thinking,然後test((i-1)%5),即test(0),其中state[(0-1)%5]state[4]!=eating成立,state[0]==hungry不成立(表示0號哲學家沒有執行pickup)state[(0+1)%5]state[1]!=eating成立,即整個if條件不成立,if下的語句不做。然後test((i+1)%5),即test(2),其中state[(2-1)%5]state[1]!=eating成立,state[2]==hungry(表示之前2號哲學家已經執行了pickup,且沒有成功取到叉子)state[(2+1)%5]state[3]!=eating不成立,即整個if條件還是不成立,然後1號哲學家執行leave(IM)退出管程,以便其他哲學家進入管程。
分析: 1號哲學在putdown的時候執行test(0)test(2)test(0)用意在於喚醒右鄰居0號哲學家,但是0號哲學家還沒有執行pickup,也就沒有把自身狀態修改為hungry1號哲學家的這份好心浪費了;接著test(2)用意在於喚醒左鄰居2號哲學家,但是2號哲學家成功取到叉子之前要判斷3號是否eating,實際上此時3號依然eating因此test(2)的用意沒有真正成功。看來還真不簡單,不要緊,繼續往下看。

(E) 當1號哲學家在putdown中退出管程,此時state[1]==thinkingstate[2]==hungry,假設此時3號哲學家開始執行putdownputdown中通過enter(IM)進入管程,並修改自身狀態state[3]=thinking然後test((i-1)%5),即test(2),其中state[(2-1)%5]state[1]!=eating成立,state[2]==hungry成立(表示之前2號哲學家已經執行了pickup,且沒有成功取到叉子)state[(2+1)%5]state[3]!=eating成立,即整個if條件成立,看到希望了,接著執行signal(self[2], self_count[2], IM),因為之前,2號哲學家已經在self[2]上等待許久了,對照signal操作的PV實現,self_count[2]>0,執行IM.next_count++V(self[2]) (:終於把2號哲學家等待許久的self[2]釋放,喚醒2號哲學家)P(IM.next)阻塞自己。
分析:對於2號哲學家被喚醒之後,他將執行其pickupP(self[2])之後的語句,self_count[2]--,即self_count[2]恢復為0,並最終執行pickupleave(IM),在leave(IM)中參考Hoare管程leave操作的PV實現,判斷if (IM.next_count)>0成立,則執行V(IM.next) 喚醒3號哲學家之前在P(IM.next)上的等待,並有機會執行後續語句IM.next_count--,即計數恢復為0,最終執行putdown中的leave(IM)退出管程,以便其他程序能夠再進入管程,這裡可以看出對於Hoare管程的實現,signal操作不必是過程體的最後一個操作。對於3號哲學家通過test(2)完成了喚醒2號哲學家的任務,然而他自己暫時阻塞在IM.next上,暫時不能執行putdown中後續的test((i+1)%5),即不能執行test(4)

(F) 接著,假設在2號哲學家被喚醒取到叉子之後開始吃,3號哲學家執行完putdown中的leave(IM)退出管程,如果1號哲學家想再一次取叉子,此時state[0]==state[3]==thinkingstate[2]==eating1號不如之前順暢,在pickup中通過enter(IM)進入管程,修改自身狀態state[1]=hungry, 接著test(1)中,state[2]!=eating不成立,即if條件不通過,因此1號哲學家沒有成功把自身狀態修改為eating,也不用做test(1)signal(self[1], self_count[1], IM)操作,再接著判斷if (state[1]!=eating)成立,因此做wait(self[1], self_count[1], IM)操作,對照wait操作的PV實現,此時,self[1]_count++表示在self[1]等待的程序數加1,然後判斷if (IM.next_count>0),如果此時2號程序還沒有執行putdown中的signal操作,那麼該條件不成立,然後執行V(IM.mutex)退出管程,接著P(self[1])阻塞自己,等待2號哲學家執行putdown中的signal操作喚醒之。

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

二、生產者消費者問題

多個生產者/多個消費者/多個緩衝單元

B : ARRAY[0..k-1] OF integer;
sput: semaphore;          /* 可以使用的空緩衝區數 */
sget: semaphore;           /* 緩衝區內可以使用的產品數 */
sput := k;                /* 緩衝區內允許放入k件產品 */
sget := 0;                /* 緩衝區內沒有產品 */
putptr, getptr : integer;       
putptr := 0; getptr := 0;
s: semaphore;             /* 互斥使用putptr, getptr */
s := 1;

process Producer_i 
begin
   L1:produce a product;
      P(sput);
      P(s1);
      B[putptr] := product;
      putptr :=(putptr+1) mod k;
      V(s1);
      V(sget);
   goto L1;
end;

process Consumer_ j        
begin
   L2:P(sget);
     P(s2);
     Product:= B[getptr];
     getptr:=(getptr+1) mod k;
     V(s2);
     V(sput);
     consume a product;
   goto L2;
end;
需要注意的是無論在生產者程序中還是在消費者程序中,兩個P操作的次序不能顛倒。

應先執行同步訊號量的P操作,然後再執行互斥訊號量的P操作,否則可能造成程序死鎖。

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

三、讀者寫者問題

寫者優先:

semaphore rmutex,wmutex,S;
    rmutex=1; wmutex=1; S=1; //增加互斥訊號量S
int readcount=0; //讀程序計數
process reader_i(  ) {
while (true)  {
       P(S);
       P(rmutex);
         if (readcount==0) P(wmutex);
       	readcount++;
       V(rmutex);
       V(S);
       讀檔案;
       P(rmutex);
       	readcount--;
        if(readcount==0)  V(wmutex);
       V(rmutex);
   }
 }
process writer_i(  ) {
    while(true) {
       P(S);
       P(wmutex);
       寫檔案;
       V(wmutex);
       V(S);
      }
  }
int readcount = 0, writecount = 0;
semaphore x=1, y=1, z=1;	// readcount,writecount互斥
semaphore rmutex=1,wmutex=1;	// 讀鎖,寫鎖

process reader        
{
  P(z);
    P(rmutex);
      P(x);
       readcount++;
       if (readcount==1) P(wmutex);
     V(x);
   V(rmutex);
  V(z);
     read;
  P(x);
     readcount--;
     if (readcount==0) V(wmutex);
  V(x)
};

process writer        
{
  P(y);
    writecount++;
    if (writecount==1) P(rmutex);
  V(y);
  P(wmutex);
     write;
  V(wmutex)
  P(y);
     writecount--;
     if (writecount==0) V(rmutex);
  V(y);
};

至今不明白z變數的作用