哲學家進餐問題-3中解決方案


問題描述

一張圓桌上坐着5名哲學家,每兩個哲學家之間的桌上擺一根筷子,桌子的中間是一碗米飯,如圖2-10所示。哲學家們傾注畢生精力用於思考和進餐,哲學家在思考時,並不影響他人。只有當哲學家飢餓的時候,才試圖拿起左、 右兩根筷子(一根一根地拿起)。如果筷子已在他人手上,則需等待。飢餓的哲學家只有同時拿到了兩根筷子才可以開始進餐,當進餐完畢后,放下筷子繼續思考。

問題分析

1) 關系分析。5名哲學家與左右鄰居對其中間筷子的訪問是互斥關系。

2) 整理思路。顯然這里有五個進程。本題的關鍵是如何讓一個哲學家拿到左右兩個筷子而不造成死鎖或者飢餓現象。那么解決方法有兩個,一個是讓他們同時拿兩個筷子;二是對每個哲學家的動作制定規則,避免飢餓或者死鎖現象的發生。



一共5個哲學家,編號0 ~4, 5支筷子編號也是0 ~4, 0號哲學家右手的筷子編號是0號,逆時針增加,哲學家的編號也是逆時針增加所以:
0號哲學家對應的是: 4號和1號筷子.
1號哲學家對應的是: 0號和2號筷子.
2號哲學家對應的是: 1號和3號筷子.
3號哲學家對應的是: 2號和4號筷子.
4號哲學家對應的是: 3號和0號筷子.
所以有宏定義:
#define left(phi_id) (phi_id+N-1)%N
#define right(phi_id) (phi_id+1)%N
N = 5

5支筷子對應5個互斥鎖,所以:

pthread_mutex_t forks[N]={PTHREAD_MUTEX_INITIALIZER};

哲學家線程需要執行的動作是:

void take_forks(int id){
    //獲取左右兩邊的筷子
    printf("Pil[%d], left[%d], right[%d]\n", id, left(id), right(id));
    pthread_mutex_lock(&forks[left(id)]);
    pthread_mutex_lock(&forks[right(id)]);
    //printf("philosopher[%d]  take_forks...\n", id);
}

void put_down_forks(int id){
    printf("philosopher[%d] is put_down_forks...\n", id);
    pthread_mutex_unlock(&forks[left(id)]);
    pthread_mutex_unlock(&forks[right(id)]);
}

void* philosopher_work(void *arg){
    int id = *(int*)arg;
    printf("philosopher init [%d] \n", id);
    while(1){
        thinking(id);
        take_forks(id);
        eating(id);
        put_down_forks(id);
    }
}

該算法存在以下問題:當五個哲學家都想要進餐,分別拿起他們左邊筷子的時候(都恰好執行完

pthread_mutex_unlock(&forks[left(id)]);)

筷子已經被拿光了,等到他們再想拿右邊的筷子的時候(執行

pthread_mutex_unlock(&forks[right(id)]);

)就全被阻塞了,這就出現了死鎖。

為了防止死鎖的發生,可以對哲學家進程施加一些限制條件,一種解決方案是:

原理:僅當哲學家的左右兩支筷子都可用時,才允許他拿起筷子進餐。 
方法1:利用AND 型信號量機制實現:根據課程講述,在一個原語中,將一段代碼同時需 
要的多個臨界資源,要么全部分配給它,要么一個都不分配,因此不會出現死鎖的情形。當 
某些資源不夠時阻塞調用進程;由於等待隊列的存在,使得對資源的請求滿足FIFO 的要求, 
因此不會出現飢餓的情形。 

但是我並沒有找到and型信號量的定義,所以不能使用.

方法2:利用信號量的保護機制實現。通過信號量mutex對eat()之前的取左側和右側筷 
子的操作進行保護,使之成為一個原子操作,這樣可以防止死鎖的出現。 

void* philosopher_work(void *arg){
    int id = *(int*)arg;
    printf("philosopher init [%d] \n", id);
    while(1){
        thinking(id);
        pthread_mutex_lock(&mutex);
        take_forks(id);
        pthread_mutex_unlock(&mutex);
        eating(id);
        put_down_forks(id);
    }
}

這個代碼有個問題就是同一時間只能有一個哲學家取筷子,效率比較低.但是可以防止死鎖

另一種解決方案是:

至多四個人拿起左邊筷子。。保證至少有一個人可以用餐,那么就能解決了,添加一個信號量room賦值等於4.代碼如下.

/*************
 *           every philosopher is in while loop: thinking -> take_forks -> eating -> put_down_forks -> thingking
 *
 *           對於可能產生的死鎖問題,我們這里采用一中解決的辦法,那就是只有當哲學接的左右兩只筷子均處於可用狀態時,
 *           才允許他拿起筷子。這樣就可以避免他們同時拿起筷子就餐,導致死鎖。
 *
 *           如果2號哲學家在吃飯那么1號和3號就必須是在思考.
 *
 *
 *
 *
 *
 ************/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>

#define N 5 // five philosopher
#define T_EAT 5
#define T_THINK 5
#define N_ROOM  4  //同一時間只允許4人用餐
#define left(phi_id) (phi_id+N-1)%N
#define right(phi_id) (phi_id+1)%N

enum { think , hungry , eat  }phi_state[N];
sem_t chopstick[N];
sem_t room;

void thinking(int id){
    sleep(T_THINK);
    printf("philosopher[%d] is thinking...\n", id);
}

void eating(int id){
    sleep(T_EAT);
    printf("philosopher[%d] is eating...\n", id);
}

void take_forks(int id){
    //獲取左右兩邊的筷子
    //printf("Pil[%d], left[%d], right[%d]\n", id, left(id), right(id));
    sem_wait(&chopstick[left(id)]);
    sem_wait(&chopstick[right(id)]);
    //printf("philosopher[%d]  take_forks...\n", id);
}

void put_down_forks(int id){
    printf("philosopher[%d] is put_down_forks...\n", id);
    sem_post(&chopstick[left(id)]);
    sem_post(&chopstick[right(id)]);
}

void* philosopher_work(void *arg){
    int id = *(int*)arg;
    printf("philosopher init [%d] \n", id);
    while(1){
        thinking(id);
        sem_wait(&room);
        take_forks(id);
        sem_post(&room);
        eating(id);
        put_down_forks(id);
    }
}

int main(){
    pthread_t phiTid[N];
    int i;
    int err;
    int *id=(int *)malloc(sizeof(int)*N);

    //initilize semaphore
    for (i = 0; i < N; i++)
    {
        if(sem_init(&chopstick[i], 0, 1) != 0)
        {
            printf("init forks error\n");
        }
    }

    sem_init(&room, 0, N_ROOM);

    for(i=0; i < N; ++i){
        //printf("i ==%d\n", i);
        id[i] = i;
        err = pthread_create(&phiTid[i], NULL, philosopher_work, (void*)(&id[i])); //這種情況生成的thread id是0,1,2,3,4
        if (err != 0)
            printf("can't create process for reader\n");
    }

    while(1);

    // delete the source of semaphore
    for (i = 0; i < N; i++)
    {
        err = sem_destroy(&chopstick[i]);
        if (err != 0)
        {
            printf("can't destory semaphore\n");
        }
    }
    exit(0);
    return 0;
}
View Code

還有一種就是:

原理:規定奇數號的哲學家先拿起他左邊的筷子,然后再去拿他右邊的筷子;而偶數號 
的哲學家則相反.按此規定,將是1,2號哲學家競爭1號筷子,3,4號哲學家競爭3號筷子.即 
五個哲學家都競爭奇數號筷子,獲得后,再去競爭偶數號筷子,最后總會有一個哲學家能獲 
得兩支筷子而進餐。而申請不到的哲學家進入阻塞等待隊列,根FIFO原則,則先申請的哲 
學家會較先可以吃飯,因此不會出現餓死的哲學家

修改函數如下:

void take_forks(int id){
    //獲取左右兩邊的筷子
    //printf("Pil[%d], left[%d], right[%d]\n", id, left(id), right(id));
    if((id&1) == 1){
        sem_wait(&chopstick[left(id)]);
        sem_wait(&chopstick[right(id)]);
    }
    else{
        sem_wait(&chopstick[right(id)]);
        sem_wait(&chopstick[left(id)]);
    }
    //printf("philosopher[%d]  take_forks...\n", id);
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM