【Linux】实验三 进程的互斥与同步

一、 实验目的

1、了解进程的互斥与同步的概念,理解经典进程同步问题的本质
2、熟悉 Linux 的进程同步机制,掌握相关 API 的使用方法
3、能利用信号量机制,采用多种同步算法实现不会发生死锁的哲学家进餐程序

二、 实验原理

有点长,所以分开了,点击这里跳转进入!!!!!!!!!!!!!!!


三、实验内容

1、以哲学家进餐模型为依据,在 Linux 控制台环境下创立 5 个进程,用 semget 函数创立一个
信号量集〔5 个信号量,初值为 1〕,模拟哲学家的思考和进餐行为:每一位哲学家饥饿时,先拿
起左手筷子,再拿起右手筷子;筷子是临界资源,为每一支筷子定义 1 个互斥信号量;想拿到筷
子需要先对信号量做 P 操作,使用完释放筷子对信号量做 V 操作。
伪代码描述:

semaphore chopstick[5]={1,1,1,1,1};
第 i 位哲学家的活动可描述为: 
do{
printf("Philopher %d is thinking\n",i);
printf("Philopher %d is hungry\n",i);
 P(chopstick[i]); //拿左筷子
 P(chopstick[(i+1) % 5); //拿右筷子
printf("Philopher %d is eating\n",i);
 V(chopstick[i]); //放左筷子
 V(chopstick[(i+1) % 5); //放右筷子}while[true]; 

请根据伪代码编写程序 philosopher1.c,创建该组进程并运行,观察进程是否能一直运行下去。假设停滞那么发生了什么现象?请分析原因,并给出解决方法,编写程序 philosopher2.c 进行验证。

2、五位哲学家围坐在一张圆形桌子上,桌子上有一盘饺子。每一位哲学家要么思考,要么等待,要么吃饺子。为了吃饺子,哲学家必须拿起两只筷子,但是每个哲学家旁边只有一只筷子,也就是筷子数量和哲学家数量相等,所以每只筷子必须由两个哲学家共享。设计一个算法以允许哲学家吃饭。算法必须保证互斥(没有两位哲学家同时使用同一只筷子),同时还要避免死锁(每人拿着一只筷子不放,导致谁也吃不了)。
请根据哲学家就餐问题流程和上文提供的伪代码编写程序,并分析程序存在的问题及改进办法。
在这里插入图片描述
程序运行如下:
在这里插入图片描述


四、我的代码内容和现象

1、philosopher1

这个代码会发生死锁的原因是,哲学家 0 和哲学家 4 都拿起了左手边的筷子,然后都在等待右手边的筷子,而右手边的筷子分别被哲学家 1 和哲学家 3 拿起了,所以哲学家 0 和哲学家 4 无法获取右手边的筷子,从而导致死锁。

在这里插入图片描述
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <time.h>
#include <sys/wait.h>

#define NUM_PHILOSOPHERS 5
#define NUM_FORKS 5

int main() 
{
// 创建信号量集,包含 NUM_FORKS 个信号量,初始值都为 1
int semid = semget(IPC_PRIVATE, NUM_FORKS, IPC_CREAT | 0666);
if (semid < 0) 
{
    perror("semget failed");
    exit(1);
}

// 创建 NUM_PHILOSOPHERS 个进程,每个进程模拟一位哲学家的思考和进餐行为
for (int i = 0; i < NUM_PHILOSOPHERS; i++) 
{
    if (fork() == 0) 
{
    // 当前进程是哲学家 i,先拿起左手筷子,再拿起右手筷子
    struct sembuf sb[2];
    sb[0].sem_num = i;
    sb[0].sem_op = -1;
    sb[0].sem_flg = 0;
    sb[1].sem_num = (i + NUM_PHILOSOPHERS - 1) % NUM_FORKS; // 修改这行代码,使得哲学家拿起筷子的顺序反过来
    sb[1].sem_op = -1;
    sb[1].sem_flg = 0;
    semop(semid, sb, 2);
    printf("Philosopher %d is putting up left chopstick.\n", i);
    printf("Philosopher %d is putting up right chopstick.\n", i);

  // 模拟进餐行为
  printf("Philosopher %d is eating\n", i);
  sleep(1);

  // 释放筷子
  sb[0].sem_op = 1;
  sb[1].sem_op = 1;
  semop(semid, sb, 2);
  printf("Philosopher %d is putting down left chopstick.\n", i);
  printf("Philosopher %d is putting down right chopstick.\n", i);

  // 模拟思考行为
    printf("Philosopher %d is thinking\n", i);
    sleep(1);
    }
}

// 删除信号量集
semctl(semid, 0, IPC_RMID);

return 0;
}


2、philosopher2

在这里插入图片描述
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <time.h>
#include <sys/wait.h>

#define NUM_PHILOSOPHERS 5 //信号量数组,分别用于维护每一位哲学家左手和右手的筷子,避免发生死锁
#define NUM_chopstick 5   //筷子数量
/*哲学家当前状态的定义*/
#define THINKING 0//思考中
#define HUNGRY 1 //饥饿中
#define EATING 2 //吃饭中

int state[NUM_PHILOSOPHERS];//数组,存储哲学家当前的状态
int sem_id;  //用于存储信号量集的标识符。


//打印当前在思考中的哲学家
void think(int philosopher) 
{
  printf("Philosopher %d is thinking.\n", philosopher);
  sleep(rand() % 5); //模拟思考的过程,思考需要时间
}

//打印当前在拿筷子的哲学家
void take_chopstick(int philosopher) 
{
  state[philosopher] = HUNGRY;  
  printf("Philosopher %d is hungry.\n", philosopher);

  // wait for left chopstick
  struct sembuf sops;
  sops.sem_num = philosopher;
  sops.sem_op = -1; //操作数option为 -1 ,即为P操作
  sops.sem_flg = 0;
  semop(sem_id, &sops, 1);
  printf("Philosopher %d is putting up left chopstick.\n", philosopher);

  // wait for right chopstick
  sops.sem_num = (philosopher + 1) % NUM_chopstick;
  semop(sem_id, &sops, 1);
  printf("Philosopher %d is putting up right chopstick.\n", philosopher);
}

void put_chopstick(int philosopher) 
{
  state[philosopher] = THINKING;

  // 放下左筷子
  struct sembuf sops;
  sops.sem_num = philosopher;
  sops.sem_op = 1; //操作数option为 1 ,即为V操作
  sops.sem_flg = 0;
  semop(sem_id, &sops, 1);
  printf("Philosopher %d is putting down left chopstick.\n", philosopher);

  // 放下右筷子
  sops.sem_num = (philosopher + 1) % NUM_chopstick;
  semop(sem_id, &sops, 1);
  printf("Philosopher %d is putting down left chopstick.\n", philosopher);
}

//打印正在吃饭的哲学家
void eat(int philosopher) 
{
  state[philosopher] = EATING;
  printf("Philosopher %d is eating.\n", philosopher);
  sleep(rand() % 5); //模拟哲学家吃饭,吃饭需要花费一段时间
}

int main()
{
// 为随机数生成器提供种子
  srand(time(NULL));

  // 用初始值创建信号量集
  sem_id = semget(IPC_PRIVATE, NUM_chopstick, 0600);
  if (sem_id < 0) 
  {
      perror("semget failed");
      exit(1);//返回非0,表示运行有错,异常终止
  }
  semctl(sem_id, 0, SETALL, 1);

  // 创造哲学家的进程
  for (int i = 0; i < NUM_PHILOSOPHERS; i++) 
  {
    if (fork() == 0) 
    {
      // 哲学家的进程
      while (1) 
      {
        think(i);
        take_chopstick(i);
        eat(i);
        put_chopstick(i);
      }
    }
  }
  // 等待所有子程序结束
  while (wait(NULL) > 0);
  
  // 删除信号量集
  semctl(sem_id, 0, IPC_RMID);
  return 0;

}

这个代码实现了哲学家进餐问题的模拟。它使用了信号量来维护每一支筷子的互斥访问,并使用了 P 操作和 V 操作来控制对信号量的访问。
1、在主函数中,首先使用 semget 函数创建了一个信号量集,并初始化了NUM_chopstick个信号量,每个信号量的初始值都为 1。然后使用 fork 函数创建了 NUM_PHILOSOPHERS 个进程,每个进程模拟一位哲学家的思考和进餐行为。
2、每个哲学家进程都会执行一个无限循环,在循环中,它会先执行 think 函数模拟思考的过程,然后执行take_chopstick 函数模拟拿起筷子的过程。这个函数会使用 semop 函数执行 P 操作来获取左手边的筷子和右手边的筷子。
– 如果获取成功,就会执行 eat 函数模拟进餐的过程,然后执行 put_chopstick 函数模拟释放筷子的过程。这个函数会使用 semop 函数执行 V 操作来释放左手边的筷子和右手边的筷子。
3、最后,主函数会使用 wait 函数等待所有的哲学家进程结束,然后使用 semctl 函数删除信号量集。


这个程序不会发生死锁,因为

1、在程序中,哲学家的进餐行为是通过调用 take_chopstick() 函数实现的。这个函数首先会将当前哲学家的状态设置为饥饿状态,然后会调用 semop() 函数对左手筷子执行 P 操作,再对右手筷子执行 P 操作。
2、当哲学家吃饱之后,会调用 put_chopstick() 函数将左右手的筷子放回桌子上。这个函数会将当前哲学家的状态设置为思考状态,然后对左手筷子和右手筷子分别执行 V 操作,释放这两支筷子。
3、由于这个程序中没有设置资源不足的情况,也没有设置资源分配不当的情况,并且哲学家在吃饭完成后会立即释放筷子,因此该程序不会产生死锁。


五、课后习题:

1.什么是死锁?产生死锁的原因和必要条件是什么?

答:死锁是指两个或多个进程因争夺资源而相互等待,导致系统无法继续运行的状态。
产生死锁的原因有以下几种:
(1)资源不够:系统中资源数量有限,如果进程需要的资源数量超过了系统能够提供的数量,那么就会发生死锁。
(2)资源分配不当:如果资源分配不当,比如一个进程已经占用了资源 A 和资源 B,另一个进程已经占用了资源 B 和资源 C,那么这两个进程就会相互等待,从而导致死锁。
(3)资源占用不当:如果进程在使用资源的过程中不释放资源,就会导致其他进程无法获取所需资源,从而导致死锁。
产生死锁的必要条件包括四个要素:
(1)互斥条件:所谓互斥条件是指系统中的资源至多只能被一个进程占用。如果一个进程占用了资源,则其他进程就不能占用这些资源。
(2)请求与保持条件:所谓请求与保持条件是指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源正被其他进程占有,此时请求进程阻塞,但又对自己已获得的资源保持不放。

(3)不剥夺条件:所谓不剥夺条件是指进程已获得的资源在未使用完之前,不能强行剥夺。
(4)循环等待条件:所谓循环等待条件是指存在一个进程的资源请求序列{P0, P1, …, P0},使得 Pi 必须等到 Pj 释放资源后才能得到资源,其中 i≠j。
当满足上述四个条件时,就会产生死锁。


2.实验中给出的伪代码流程,实现多位哲学家就餐问题,是否会产生死锁?若会,请说明产生死锁的原因。

答:在这个伪代码中,会产生死锁的原因是:
(1)每个哲学家都在循环中进行思考、饥饿和吃饭的行为,并没有退出循环的条件。
(2)在拿筷子之前,每个哲学家都会先拿起左手的筷子,然后再拿起右手的筷子。
(3)当两个哲学家同时拿起了自己的左手筷子时,如果他们都想要拿起右手的筷子,就会产生死锁。
具体情况是这样的:哲学家 1 和哲学家 2 同时拿起了自己的左手筷子,然后同时等待右手的筷子。但是右手的筷子都被对方拿起了,所以他们就陷入了死锁。
为了避免这种情况的发生,可以使用一些机制来解决死锁问题,比如说:
(1)对于每个哲学家,使用随机数来决定先拿哪只手的筷子。
(2)在拿筷子的时候,设置一个超时时间,如果超过了这个时间还没有拿到筷子,就退出循环。
(3)使用顺序等待,让每个哲学家按照固定的顺序拿筷子,这样就不会出现同时拿起左手筷子的情况。
总之,死锁的产生是由于多个进程因为竞争临界资源。


3.针对本实验的哲学家就餐问题,如何设置信号量以避免产生死锁?

答:为了避免在本实验的哲学家就餐问题中产生死锁,可以设置一个顺序信号量,使得哲学家在拿筷子时按照固定顺序进行。比如,哲学家 0 只能先拿左边的筷子再拿右边的筷子,哲学家 1 只能先拿右边的筷子再拿左边的筷子,以此类推。这样,只要所有的哲学家都按照这个顺序拿筷子,就不会产生死锁的情况。


六、心得体会

完成本试验后,我认识到了信号量的作用和使用方法,了解了互斥信号量和条件信号量的区别,并了解了死锁的产生原因和如何避免死锁的方法。在实验中,我还学会了使用 semget 函数和 semop 函数来创建信号量集和进行信号量操作。
总的来说,本实验使我加深了对进程同步和死锁问题的理解,并学会了使用信号量来解决实际问题。


每天进步一点点 笔记仅供自学,用来回看复习,不一定适合你,如有错误请指出。

  • 3
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KevinGuo457

哈哈哈资助我买两包辣条叭

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值