• 操作系统第6次实验报告:使用信号量解决进程互斥访问


    0.个人信息

    • 姓名 罗廷杨
    • 学号 201821121013
    • 班级 计算1811

    1. 哲学家进餐问题

    五个哲学家围在一个圆桌就餐,每个人都必须拿起左右两把叉子才能进餐,当每个人都先拿起左叉子,等待右叉子的时候就会造成死锁。

    2. 给出伪代码

    先创建一个信号集,该信号集里面有5个信号量,用这些信号量来分别表示五把叉子,值初始化为1表示叉子可以被拿起使用。接着由主进程创建4个子进程,用这5个进程(1个父进程+4个子进程)分别来表示5个哲学家,编号由0到4,接着就可以为哲学家安排进餐(philosopere(sem_id,tk_no))。哲学家刚开始处于思考状态(think(tk_no)),经过一段时间的思考会处于饥饿状态(hungry),然后哲学家需要拿取左右两边的叉子来进餐,先拿取左边的叉子(P(sem_id,left_fno);),然后再拿取右边的叉子(P(sem_id,right_fno);),如果哲学家成功拿到两边的叉子那么就可以进餐,进餐后将叉子放回原位,先放右叉子(V(sem_id,right_fno);),再放左叉子(V(sem_id,left_fno););如果前面哲学家没有拿到左右两边的叉子,那么就要等待直到拿到左右两边的叉子后才可以进餐。该过程的伪代码如下:

    semaphore fork[5]={1,1,1,1,1};//五把叉子 
    process pro_i(i=0,1,2,3,4);//五个进程(1个父进程+4子进程) 
    philosopere(sem_id,tk_no)//tk_no哲学家编号 
    {
        while(1)
        {
            think(tk_no);//思考 
            hungry(tk_no);//饥饿 
            P(sem_id,left_fno);//left_fno:左叉子编号,拿取左手边的叉子,更改左边叉子信号量
            P(sem_id,right_fno);//right_fno右叉子编号,拿取右手边的叉子,更改右边叉子信号量
            eat(tk_no);//就餐 
            V(sem_id,right_fno);//释放右手边的叉子
            V(sem_id,left_fno);//释放左手边的叉子
        }
    }

    上面的这种先拿取左边叉子再拿取右边叉子的做法存在一个问题:当每个人都先拿起左叉子,等待右叉子的时候就会造成死锁。为了解决死锁的问题对上面的设计进行更改,由于死锁是因为每个人同时拿起左叉子导致没有一个哲学家能够拿到右叉子造成的,所以对每位哲学家拿取和放下叉子的方式进行更改,更改成同时拿取左叉子和右叉子(P(sem_id,left_fno,right_fno);),同时放下左叉子和右叉子(V(sem_id,left_fno,right_fno);),这样就保证了最多能有两名哲学家同时进餐,且进餐的哲学家所在位置不相邻,从而解决了死锁问题。更改后的伪代码如下:

    semaphore fork[5]={1,1,1,1,1};//五把叉子 
    process pro_i(i=0,1,2,3,4);//五个进程(1个父进程+4子进程) 
    void philosopere(int sem_id,int tk_no)
    {
        while(1)
        {
            think(tk_no);//思考 
            hungry(tk_no);//饥饿 
            //拿取一双叉子,同时更改该哲学家左右叉子的信号量
            P(sem_id,left_fno,right_fno);
            eat(tk_no);//就餐 
            //放下叉子 
            V(sem_id,left_fno,right_fno);
        }
    }

    需要注意的是,叉子的拿取和放回实际上是通过控制叉子所对应的信号量来实现的。叉子对应的信号量的初始值为1,当一位哲学家拿取一个位置的叉子后,那么该叉子对应的信号量就变成了0,那么下一位哲学家再拿取该位置的叉子时,那么该处的信号量就变为了-1,那么它就会阻塞当前线程,也就是不允许该哲学家进餐。当某位哲学家将叉子放回,那么该叉子对应的信号量值变为1允许下一位哲学家拿取使用。

    3. 完整代码

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/ipc.h>
    #include<sys/msg.h>
    #include<sys/types.h>
    #include<unistd.h>
    #include<errno.h>
    #include<sys/ipc.h>
    #include<sys/sem.h>
    #include<sys/wait.h>
    #define ERR_EXIT(m) 
        do { 
            perror(m); 
            exit(EXIT_FAILURE); 
        } while(0)
    
    #define SEMS_NUM 5  //信号量数目 
    #define THINKER_NUM 5 //哲学家人数 
    #define FORK_NUM 5 //叉子数 
    #define TIME_WAIT (rand() % 5 + 1) //等待时间 
    
    union semun
    {
        int val;    //SETVAL用的值
        struct semid_ds *buf;  //IPC_STAT、IPC_SET用的semid_ds结构
        unsigned short  *array; //SETALL、GETALL用的数组值
        struct seminfo  *__buf; //为控制IPC_INFO提供的缓存
    };
    
    void think(int tk_no);//思考 
    void eat(int tk_no);//就餐 
    void hungry(int tk_no);//饥饿 
    //哲学家拿取两把叉子,如果未拿到叉子则进餐受阻 
    void P(int sem_id,int left_fno,int right_fno);
    //哲学家放回叉子 
    void V(int sem_id,int left_fno,int right_fno);
    void philosopere(int sem_id,int tk_no);//就餐安排 
    
    int main()
    {
        //1.创建 Semaphore(计数信号量)
        //创建信号集,其中有SEMS_NUM个信号量 
        int sem_id = semget(IPC_PRIVATE, SEMS_NUM, IPC_CREAT | 0666);
        if (sem_id == -1){
            ERR_EXIT("semget");
        }
            
        //2.初始化 Semaphore
        union semun sem;
        sem.val = 1;
        int i;
        for (i = 0; i < SEMS_NUM; i++){
            if(semctl(sem_id, i, SETVAL, sem)==-1){
                ERR_EXIT("semctl");
            }
        }
    
        int tk_no = 0;//哲学家编号 
        pid_t pid;
        //创建子进程 
        for (i = 1; i < THINKER_NUM; i++)
        {
            pid = fork();
            if (pid == -1){
                ERR_EXIT("fork");
            }
            if (pid == 0){
                tk_no = i;
                break;
            }
        }
        
        philosopere(sem_id,tk_no);//处理就餐 
    
        return 0;
    }
    /*对信号量数组编号为no的信号量做P操作
      哲学家拿取两把叉子,如果未拿到则进餐受阻 
    */
    void P(int sem_id,int left_fno,int right_fno)
    {     
        /*
             sbuf.sem_num:序号
             sbuf.sem_op:操作,-1表示P操作 
             sem_flg:设置信号量的操作 
        */
         struct sembuf sbuf[2] =
         {
            {left_fno, -1, SEM_UNDO},
            {right_fno, -1, SEM_UNDO}
         };
         
         if(semop(sem_id, sbuf, 2) == -1) {
             ERR_EXIT("P");
         }
    }
    /*对信号量数组编号为no的信号量做V操作
      哲学家放回叉子
    */
    void V(int sem_id,int left_fno,int right_fno){
         
        /*
             sbuf.sem_num:序号
             sbuf.sem_op:操作,1表示V操作 
             sem_flg:设置信号量的操作 
        */
         struct sembuf sbuf[2] =
         {
            {left_fno, 1, SEM_UNDO},
            {right_fno, 1, SEM_UNDO}
         };
         
         if(semop(sem_id, sbuf, 2) == -1) {
              ERR_EXIT("V");
         }
    }
    
    
    void think(int tk_no){
        printf("哲学家%d正在思考
    ", tk_no);
        sleep(TIME_WAIT);
    }
    
    void eat(int tk_no){
        printf("哲学家%d正在就餐
    ", tk_no);
        sleep(TIME_WAIT);
    }
    
    void hungry(int tk_no){
        printf("哲学家%d饿了,需要就餐
    ", tk_no);
    } 
    
    void philosopere(int sem_id,int tk_no)
    {
        srand(getpid());
        int left_fno=tk_no; //left_fno左边叉子编号 
        int right_fno=(left_fno+1)%FORK_NUM;
        while(1)
        {
            think(tk_no);//思考 
            hungry(tk_no);//饥饿 
            //拿取一双叉子 
            P(sem_id,left_fno,right_fno);
            eat(tk_no);//就餐 
            //放下叉子 
            V(sem_id,left_fno,right_fno);
        }
    }

    4. 运行结果并解释

    运行结果

     ......

    结果解释

    五个进程(1个父进程+4子进程)分别对应5个哲学家,他们的编号从0~4。进餐的过程是,哲学家先进行思考,思考过后饿了需要进餐, 然后需要同时拿起自己左右两把叉子进行就餐,如果没有拿到左右两把叉子那么就需要等待不能就餐 。因为只有同时拿到左右两把叉子才能就餐,所以在结果中可以看到五个哲学家至多有两个哲学家同时就餐且这两个哲学家所坐的位置不相邻。 在结果中还可以观察到就餐过程一直执行没有停止,说明没有发生死锁现象。

    5.参考资料

    【1】https://www.cnblogs.com/alantu2018/p/8473159.html

    【2】https://blog.csdn.net/guoping16/article/details/6584043

  • 相关阅读:
    栈:删除最外层的括号 (Leetcode 1021 / 155 / 1172 / 剑指31 / 面试 03.03)
    JDBC: Druid连接池
    JDBC: C3P0
    JDBC: C3P0
    JDBC: C3P0连接池
    JDBC: DBCP连接池
    JDBC: 数据库连接池
    JDBC: JDBC 控制事务
    JDBC: 预处理对象
    wpf 键盘快捷键响应
  • 原文地址:https://www.cnblogs.com/lty1661489001/p/12973598.html
Copyright © 2020-2023  润新知