C++死鎖與哲學家就餐問題

“操作系統”專欄內含課設報告+實驗報告+期末複習整理   C++存儲管理算法設計之【內存空間的分配和回收】

設計一  採用預先分配法預防死鎖的哲學家就餐問題

1. 實驗目的

       理解死鎖的概念,掌握死鎖預防方法。

        死鎖是進程併發執行過程中可能出現的現象,哲學家就餐問題是描述死鎖的經典例子。爲了防止死鎖,可以採用資源預先分配法。資源預先分配法是指進程在運行前一次性地向系統申請它所需要的全部資源,如果系統當前不能夠滿足進程的全部資源請求,則不分配資源, 此進程暫不投入運行,如果系統當前能夠滿足進程的全部資源請求, 則一次性地將所申請的資源全部分配給申請進程。在哲學家就餐問題中,要採用資源預先分配法只需讓每個哲學家同時申請左右兩根筷子。

2. 實驗要求

        利用多線程技術編寫哲學家就餐程序,演示採用死鎖防止方法後不產生死鎖的情況。

3. 實驗步驟

3.1 程序結構設計

       程序需要六個線程,主線程用於顯示功能描述;五個哲學家線程用於模擬哲學家的活動,即不停地思考、飢餓、進食。相鄰的兩個哲學家線程需要共享他們中間的同一根筷子,因此對每一根筷子的使用要互斥,用互斥體數組h_mutex_chopsticks來實現。主線程創建五個哲學家線程後要等待所有哲學家結束,用線程句柄數組h_thread來表示五個線程,主線程通過等待這五個線程句柄來實現同步。

      該程序共有7個函數,這些函數可以分成4組。各組包含的函數及其功能如圖4-1所示。

組別

包括函數

函數功能

main()

顯示主菜單,接收用戶的選擇並執行相應的功能。

deadlock_philosopher()

deadlock()

演示死鎖情況的哲學家線程函數

初始化函數:創建五個哲學家並等待它們結束

ordered_allocation_philosopher()

ordered_allocation()

通過按序分配法防止死鎖的哲學家線程函數

初始化函數:創建五個哲學家並等待它們結束

pre_allocation_philosopher()

pre_allocation()

通過預先分配法防止死鎖的哲學家線程函數

初始化函數:創建五個哲學家並等待它們結束

                                            圖4-1  函數及其功能

3.2 算法設計

       下面給出預分配法函數pre _allocation_philosopher和初始化函數pre_allocation的算法描述。

設計二 採用有序分配法預防死鎖的哲學家就餐問題

1. 實驗目的

        理解死鎖的概念,掌握死鎖預防方法。

        死鎖是進程併發執行過程中可能出現的現象,哲學家就餐問題是描述死鎖的經典例子。

        爲了防止死鎖,可以採用資源有序分配法。資源有序分配法是指事先將所有資源類全排序, 即賦予每一個資源類一個唯一的整數,規定進程必需按照資源編號由小到大的次序申請資源。

        在哲學家就餐問題中,要採用資源有序分配法只需規定每個哲學家先申請左右兩根筷子中編號小的筷子,再申請編號大的筷子。

2. 實驗要求

        利用多線程技術編寫哲學家就餐程序,演示採用死鎖防止方法後不產生死鎖的情況。

3.2 算法設計

設計三  不預防死鎖情況下的哲學家就餐問題

1. 實驗目的

        理解死鎖的概念。

       死鎖是進程併發執行過程中可能出現的現象,哲學家就餐問題是描述死鎖的經典例子。假設有幾位哲學家圍坐在一張餐桌旁,桌上有喫不盡的食品,每兩位哲學家之間擺放着一根筷子,筷子的個數與哲學家的數量相等,每一位哲學家要麼思考,要麼等待,要麼拿起左右兩根筷子進餐。本設計假設有五個哲學家和五根筷子,它們的編號都是從0到4。 如果每位哲學家都拿起左邊的筷子,就會發生死鎖。

2. 實驗要求

       利用多線程技術編寫哲學家就餐程序,使之在運行時能演示產生死鎖的情況。

3.2 算法設計

       在windows中可以用系統調用WaitForMultipleObjects()同時申請兩份資源,但是在linux中沒有相應的系統調用,因此要在linux下實現資源預分配法,就要自己編寫同時申請兩根筷子的函數。這需要將哲學家的狀態增至三個, 即思考、飢俄、進食,每個哲學家僅在飢俄時才申請筷子,而且同時申請其左右兩根筷子,如果此時左右兩根子不同時空閒,則哲學家將等待。具體解法如下所示。

#define N 5
    typedef enum{thinking, hungry, eating}status;
    status state[N];
    semaphore self[N];
semaphore mutex = 1;

void test(int i)
    {
if((state[i] == hungry)&&
      (state[(i-1)%N] != eating)&&
      (state[(i+1)%N] != eating)){
          state[i] = eating;
          V(self[i]);
      }
}

void pick_chopsticks(int i)
    {
         P(mutex);
         state[i] = hungry;
         test(i);
         V(mutex);
         P(self[i]);
 }

void put_chopsticks(int i)
    {
         P(mutex);
         state[i] = thinking;
         test((i-1)%N);
         test((i+1)%N);
         V(mutex);
}

    void philosopher(int i)
    {
       while(1){
         think();
         pick_chopsticks(i);
         eat();
         put_chopsticks(i);
    }
   
void main
{
    int i;
    for(i=0;i<5;i++){
state[i] = thingking;
self[i].value = 0;
        }
    }

       在上述程序中, 自定義數據類型status用來枚舉哲學家的狀態,數組state用來存放五個哲學家的狀態,由於該數組是全局變量,所以用信號燈變量mutex實現對它的互斥訪問。信號量數組self包含五個元素,每個元素的初始值皆爲0,當第i號哲學家不具備進食條件時,會將自己阻塞在信號量self[i]上。函數test用於測試i號哲學家是否具備進食的條件。i號哲學家可以進食必須同時滿足以下條件:i號哲學家飢餓,左邊哲學家不在進食,右邊哲學家不在進食。

【程序代碼】

#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <curses.h>
#include <time.h>
#include <semaphore.h>
#include <string.h>
#define MAX_PHILOSOPHERS 5 
#define ZERO 48  
#define DELAY (rand()%25)/1000

typedef enum {thinking,hungry,eating}status;   //枚舉哲學家的狀態
status state[MAX_PHILOSOPHERS];   //存放五個哲學家的狀態,全局變量
pthread_mutex_t pre_mutex;  //所以用信號燈變量mutex實現對state的互斥訪問 
sem_t pre_self[MAX_PHILOSOPHERS];  //信號量數組self包含五個元素,每個元素的初始值皆爲0
pthread_mutex_t h_mutex_chopsticks[MAX_PHILOSOPHERS]  //互斥體數組實現對每一根筷子的互斥使用
int thread_number[MAX_PHILOSOPHERS]={0,1,2,3,4};  

void pre_test(int i);
void pre_pick_fork(int i);
void pre_put_fork(int i);

//演示死鎖情況的哲學家線程函數
void* deadlock_philosopher(void* data){
	int philosopher_number=*(int *)(data);
	int i=0;

	for(;;)
	{
		srand( (unsigned)time( NULL ) * ( philosopher_number+ 1) );  //隨機等待一段時間
       sleep(DELAY);
		if(i>=5){
			i=0;
			clear();
			refresh();
		}
		else
			i++;

		printw("%s%c%s%c\n","Philosopher ",ZERO+philosopher_number,
" is waiting chopstick ",ZERO+philosopher_number);     //提示等待左筷子
		refresh();
		pthread_mutex_lock(&h_mutex_chopsticks[philosopher_number]);
		
	    sleep(DELAY/4);    //隨機等待一段時間

		printw("%s%c%s%c\n","Philosopher ",ZERO+philosopher_number,
" is waiting chopstick ",ZERO+(1+philosopher_number)%MAX_PHILOSOPHERS);
		refresh();
		pthread_mutex_lock(
&h_mutex_chopsticks[(1+philosopher_number)%MAX_PHILOSOPHERS]
);    //申請右筷子
	  	printw("%s%c%s\n","Philosopher",ZERO+philosopher_number,"is eating.");
//提示正在進餐
		refresh();

       sleep(DELAY);
		
		printw("%s%c%s%c\n","Philosopher ",ZERO+philosopher_number,
" is releasing chopstick ",ZERO+philosopher_number);  //放下左筷子
		refresh();
		pthread_mutex_unlock(&h_mutex_chopsticks[philosopher_number]);
		printw("%s%c%s%c\n","Philosopher ",ZERO+philosopher_number,
"is releasing chopstick ", ZERO+(1+philosopher_number)   //放下右筷子
%MAX_PHILOSOPHERS);
		refresh();
       pthread_mutex_unlock(
&h_mutex_chopsticks[(1+philosopher_number)%MAX_PHILOSOPHERS]
);
		sleep(DELAY);
	} 
	return 0;
}

//初始化函數:創建五個哲學家並等待它們結束
void deadlock(){
	int i=0;
	pthread_t h_thread[MAX_PHILOSOPHERS];

	printw("deadlock possible.\n");
	refresh();
	for(i=0;i<MAX_PHILOSOPHERS;i++){
		pthread_mutex_init(&h_mutex_chopsticks[i],NULL);
	};
	for(i=0;i<MAX_PHILOSOPHERS;i++){
		pthread_create(&h_thread[i],NULL,deadlock_philosopher,&thread_number[i]);
	};
	for(i=0;i<MAX_PHILOSOPHERS;i++){
		pthread_join(h_thread[i],NULL);
	}	
}

//通過按序分配法防止死鎖的哲學家線程函數
void* ordered_allocation_philosopher(void* data){
	int philosopher_number=*(int *)(data);
	int i=0;

	for(;;)
	{
		srand( (unsigned)time( NULL ) * ( philosopher_number+ 1) ); //隨機等待一段時間
       sleep(DELAY);
		if(i>=5){
			i=0;
			clear();
			refresh();
		}
		else
			i++;
	       if(philosopher_number==MAX_PHILOSOPHERS-1){
			printw("%s%c%s%c\n","Philosopher ",ZERO+philosopher_number,
" is waiting chopstick ",ZERO+(1+philosopher_number)%MAX_PHILOSOPHERS);
		   refresh();   //提示等待左右兩邊編號較小的筷子
		   pthread_mutex_lock(
&h_mutex_chopsticks[(1+philosopher_number)%MAX_PHILOSOPHERS]);  
//申請編號較小的筷子
          sleep(DELAY/4);  //隨機等待一段時間
	    printw("%s%c%s%c\n","Philosopher ",ZERO+philosopher_number," is waiting chopstick ",ZERO+philosopher_number);
			refresh();
			pthread_mutex_lock(&h_mutex_chopsticks[philosopher_number]);
		}  //提示等待左右兩邊編號較大的筷子
		else{
			printw("%s%c%s%c\n","Philosopher ",ZERO+philosopher_number," is waiting chopstick ",ZERO+philosopher_number);
		   refresh();
		   pthread_mutex_lock(&h_mutex_chopsticks[philosopher_number]);
		   sleep(DELAY/4);
		  printw("%s%c%s%c\n","Philosopher ",ZERO+philosopher_number," is waiting chopstick ",ZERO+(1+philosopher_number)%MAX_PHILOSOPHERS);
		   refresh();   //提示正在進餐
		   pthread_mutex_lock(
&h_mutex_chopsticks[(1+philosopher_number)%MAX_PHILOSOPHERS]);
		}
	  printw("%s%c%s\n","Philosopher ",ZERO+philosopher_number," is eating.");
		refresh();
	
       sleep(DELAY);
		printw("%s%c%s%c\n","Philosopher ",ZERO+philosopher_number," is releasing chopstick ",ZERO+philosopher_number);  //放下編號較小的筷子
		refresh();
		pthread_mutex_unlock(&h_mutex_chopsticks[philosopher_number]);
	 printw("%s%c%s%c\n","Philosopher ",ZERO+philosopher_number," is releasing chopstick ",ZERO+(1+philosopher_number)%MAX_PHILOSOPHERS);
		refresh();     //放下編號較大的筷子
pthread_mutex_unlock(&h_mutex_chopsticks[(1+philosopher_number)%MAX_PHILOSOPHERS]
);
	sleep(DELAY);
	} 
	return 0;
}

//初始化函數:創建五個哲學家並等待它們結束
void* ordered_allocation(){
	int i=0;
	pthread_t h_thread[MAX_PHILOSOPHERS];  //線程句柄數組h_thread來表示五個線程,主線程通過等待這五個線程句柄來實現同步

	printw("orderded allocation:deadlock impossible.\n");
	refresh();
	for(i=0;i<MAX_PHILOSOPHERS;i++){
		pthread_mutex_init(&h_mutex_chopsticks[i],NULL);
	};
	for(i=0;i<MAX_PHILOSOPHERS;i++){
		pthread_create(&h_thread[i],NULL,ordered_allocation_philosopher,&thread_number[i]);
	};

	for(i=0;i<MAX_PHILOSOPHERS;i++){
		pthread_join(h_thread[i],NULL);
	}
}

//通過預先分配法防止死鎖的哲學家線程函數
void* pre_allocation_philosopher(void* data){
	int philosopher_number=*((int*)(data));
	int i=0;
	
	for(;;)
	{
		srand( (unsigned)time( NULL ) * ( philosopher_number+ 1) );
       sleep(DELAY);     //隨機等待一段時間
		if(i>=10){
			i=0;
			clear();
			refresh();
		}
		else
			i++;
	printw("%s%c%s\n","Philosopher ",ZERO+philosopher_number,"is thinking ");
		refresh();        //提示等待左邊筷子
		state[philosopher_number]=thinking;
		pre_pick_fork(philosopher_number);
	 printw("%s%c%s\n","Philosopher ",ZERO+philosopher_number," is eating.");
		refresh();       //提示等待右筷子
		state[philosopher_number]=eating;
	    sleep(DELAY);
		pre_put_fork(philosopher_number);
		sleep(DELAY);
	}
	return 0;
}

void pre_pick_fork(int i){
	pthread_mutex_lock(&pre_mutex);
	state[i]=hungry;
	printw("%s%c%s\n","Philosopher ",ZERO+i," is hungry. ");
	pre_test(i);
	pthread_mutex_unlock(&pre_mutex);
	sem_wait(&pre_self[i]);
}

void pre_put_fork(int i){
	pthread_mutex_lock(&pre_mutex);
	state[i]=thinking;
	pre_test((i-1)%MAX_PHILOSOPHERS);         //放下左筷子
	pre_test((i+1)%MAX_PHILOSOPHERS);        //放下右筷子
	pthread_mutex_unlock(&pre_mutex);
}
void pre_test(int i){
	if((state[i]==hungry)
&&(state[(i-1)%MAX_PHILOSOPHERS]!=eating)
&&(state[(i+1)%MAX_PHILOSOPHERS]!=eating)){
		state[i]=eating;                           //提示正在就餐
		sem_post(&pre_self[i]);
	}
}

//初始化函數:創建五個哲學家並等待它們結束
void pre_alloction(){
	int i=0;
	pthread_t h_thread[MAX_PHILOSOPHERS];
	pthread_mutex_init(&pre_mutex,NULL);

	printw("pre_allocation:deadlock impossible.\n");
	refresh();
	for(i=0;i<MAX_PHILOSOPHERS;i++){
		sem_init(&pre_self[i],0,0);
		state[i]=thinking;
	};
	for(i=0;i<MAX_PHILOSOPHERS;i++){
		pthread_create(&h_thread[i],NULL,pre_allocation_philosopher,&thread_number[i]);
	};

	for(i=0;i<MAX_PHILOSOPHERS;i++){
		pthread_join(h_thread[i],NULL);
	}
	pthread_mutex_destroy(&pre_mutex);
}

//顯示主菜單,接收用戶的選擇並執行相應的功能。
int main(int argc,char *argv[]){
	char select;
	bool end=false;
	initscr();
	while(!end){
		clear();
		refresh();
		printw("|-----------------------------------------|\n");
		printw("|  1:deadlock                             |\n");
		printw("|  2:non_deadlock by ordered allocation   |\n");
		printw("|  3:non_deadlock by pre_allocation       |\n");
		printw("|  4:exit                                 |\n");
		printw("|-----------------------------------------|\n");
		printw("select a function(1~4):");
		do{
			select=(char)getch();
		}while(select!='1'&&select!='2'&&select!='3'&&select!='4');
		clear();
		refresh();
		switch(select){
		case '1':
			deadlock();
			break;
		case '2':
			ordered_allocation();
			break;
		case '3':
			pre_alloction();
			break;
		case '4':
			end=true;
		}
		printw("\nPress any key to return to main menu.");
		getch();
		clear();
		refresh();
	}
	endwin();
	return 0;
}

【實驗結果】

【實驗心得】

       由荷蘭學者Dijkstra提出的哲學家進餐問題(The Dinning Philosophers Problem)是經典的同步問題之一。哲學家進餐問題是一大類併發控制問題的典型例子,涉及信號量機制、管程機制以及死鎖等操作系統中關鍵問題的應用,在操作系統文化史上具有非常重要的地位。對該問題的剖析有助於深刻地理解計算機系統中的資源共享、進程同步機制、死鎖等問題,並能熟練地將該問題的解決思想應用於生活中的控制流程。通過本次實驗,我受益匪淺。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章