1. 程式人生 > >System V 訊號量(三)之用訊號量解決哲學家進餐問題

System V 訊號量(三)之用訊號量解決哲學家進餐問題

一.哲學家就餐問題


          5個哲學家,5個筷子。5個哲學家圍坐在一張桌子上,筷子放在分別放在每個哲學家的兩旁。如果所有哲學家在某個時刻同時拿起左邊的筷子,那麼右邊的筷子就都被其他的哲學家拿了,造成大家都無法吃飯。但是大家都不想放下左邊的筷子(規則是先拿起左邊筷子在拿起右邊的,吃完飯在放下左,再放下右),這就是死鎖。
解決這個問題有個辦法是在拿起筷子前先判斷左右兩個筷子是否可用,可用才能拿,而且是同時拿,這樣不相鄰的哲學家就可以吃上飯,不會造成死鎖。

用虛擬碼描述下5位哲學家主要做的事,暫時還沒有考慮死鎖問題.

程式中,哲學家編號為0-4,筷子編號也為0到4

void philosopher(int i)

   // i:哲學家編號,從0到4
{
    while(TRUE)

    {
        think()
;                 // 哲學家思考
        take_fork(i);         //餓了,拿起左筷子
        take_fork((i+1)%N);  // 拿起右筷子
        eat();                     // 進食
        put_fork(i);          // 放下左筷子
        put_fork((i+1)%N);  // 放下右筷子
    }
}

二.用訊號量解決哲學家就餐問題

容易出現死鎖用的是記錄型訊號量,虛擬碼為:

semaphore  chopstick chopstick[5] = {1,1,1,1,1};

do
    {
        //think
        wait(chopstick[i]);
        wait(chopstick[(i+1)%5]);
        //eat
        signal(chopstick[i]);
        signal(chopstick[(i+1)%5]);
    }while(true)

沒有死鎖用的是AND訊號量解決的,虛擬碼為:

semaphore  chopstick chopstick[5] = {1,1,1,1,1};

do
    {
        //think
        Sswait(chopstick[i],chopstick[(i+1)%5]);
        //eat
        Ssignal(chopstick[i],chopstick[(i+1)%5]);
    }while(true)

<pre name="code" class="cpp">#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
 
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>
 

union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo *__buf;
};
 
 
#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)
     

//申請一個資源
int	wait_1fork(int no,int semid)
{
    //int left = no;
    //int right = (no + 1) % 5;
    struct sembuf sb = {no,-1,0};
    int ret;
    ret = semop(semid,&sb,1);
    if(ret < 0) {
        ERR_EXIT("semop");
    }
    return ret;
}
 
// 釋放一個資源
int free_1fork(int no,int semid)
{
    struct sembuf sb = {no,1,0};
    int ret;
    ret = semop(semid,&sb,1);
    if(ret < 0) {
        ERR_EXIT("semop");
    }
    return ret;
}
 
//這裡表明叉子是一個臨界資源

#define DELAY (rand() % 5 + 1)

//相當於P操作
void wait_for_2fork(int no,int semid)
{
    //哲學家左邊的刀叉編號和哲學家是一樣的
    int left = no;
    //右邊的刀叉
    int right = (no + 1) % 5;
 
    //刀叉值是兩個
    //注意第一個引數是編號
    //操作的是兩個訊號量,即兩種資源都滿足,才進行操作
    struct sembuf buf[2] = {
        {left,-1,0},
        {right,-1,0}
    };
    //訊號集中有5個訊號量,只是對其中的資源sembuf進行操作
    semop(semid,buf,2);
}
 
//相當於V操作  ,釋放刀叉
void free_2fork(int no,int semid)
{
    int left = no;
    int right = (no + 1) % 5;
    struct sembuf buf[2] = {
        {left,1,0},
        {right,1,0}
    };
    semop(semid,buf,2);
}
 
 
//哲學家要做的事
void philosophere(int no,int semid)
{
    srand(getpid());
    //srand(time(NULL));
    for(;;) 
    {
    #if 1
        //這裡採取的措施是當兩把刀叉都可用的時候(即兩種資源都滿足的時候)
        //哲學家才能吃飯,這樣不相鄰的哲學家就可吃上飯
        printf("%d is thinking\n",no);  // 思考中
        sleep(DELAY);
        printf("%d is hungry\n",no);  // 感覺到飢餓
        wait_for_2fork(no,semid);//拿到兩把叉子才能吃飯
        printf("%d is eating\n",no);  // 吃飯
        sleep(DELAY);
        free_2fork(no,semid);//釋放兩把叉子
    #else
        //這段程式碼可能會造成死鎖
        int left = no;
        int right = (no + 1) % 5;
        printf("%d is thinking\n",no);  // 思考中
        sleep(DELAY); 
        printf("%d is hungry\n",no);   // 感覺到飢餓
        wait_1fork(left,semid);    // 拿起左叉子,現在是隻要有一個資源,就申請
        sleep(DELAY);            
        wait_1fork(right,semid);   // 拿到右叉子
        printf("%d is eating\n",no);  // 吃飯
        sleep(DELAY);
        free_1fork(left,semid); // 釋放左叉子
        free_1fork(right,semid);  // 釋放右叉子
    #endif
    }
}
 

int main(int argc,char *argv[])
{
    int semid;
    //建立訊號量
     //訊號量集中5個訊號量
    semid = semget(IPC_PRIVATE,5,IPC_CREAT | 0666); 
    if(semid < 0) {
        ERR_EXIT("semid");
    }
    union semun su;
    su.val = 1;
    int i;
    for(i = 0;i < 5;++i) {
        //注意第二個引數也是索引
        semctl(semid,i,SETVAL,su);
    }
    //建立4個子程序
    int num = 0;
    pid_t pid;
    for(i = 1;i < 5;++i) 
    {
       pid = fork(); 
       if(pid < 0) 
        {
           ERR_EXIT("fork");
        }
        if(0 == pid)  // 子程序
        {
            num = i;
            break;
        }
    }
    //這裡就是哲學家要做的事情
   philosophere(num,semid);
    return 0;
}



一直執行,代表不出現死鎖:

修改101行程式碼為 #if  0  ,演示出現死鎖現象,如果沒有出現,多執行幾次試試

相關參考:
       <<計算機作業系統>>西安電子科技出版社(第四版) 64頁