哲学家问题(死锁问题)

1.问题描述

有五个哲学家绕着圆桌坐,每个哲学家面前有一盘面,两人之间有一支筷子,这样每个哲学家左右各有一支筷子。哲学家有2个状态,思考或者拿起筷子吃饭。如果哲学家拿到一只筷子,不能吃饭,直到拿到2只才能吃饭,并且一次只能拿起身边的一支筷子。一旦拿起便不会放下筷子直到把饭吃完,此时才把这双筷子放回原处。如果,很不幸地,每个哲学家拿起他或她左边的筷子,那么就没有人可以吃到饭了。
哲学家进餐问题是一个多线程运用的经典例子,涉及到线程同步/互斥,临界区访问问题以及死锁问题。
**

2 解题思路

**
​ 因为是五位哲学家,并且每位哲学家的各自做自己的事情(思考和吃饭),因此可以创建五个线程表示五位哲学家,五个线程相互独立(异步)。并对五位哲学家分别编号为0~4。

​ 同时,有五根筷子,每根筷子只对其相邻的两位哲学家是共享的,因此这五根筷子可以看做是五种不同的临界资源。并对五根筷子分别编号为0~4,其中第i号哲学家左边的筷子编号为i,则其右边的筷子编号就应该为(i + 1) % 5。

因为筷子是临界资源,因此当一个线程在使用某根筷子的时候,应该给这根筷子加锁,使其不能被其他进程使用。

​ 根据以上分析,可以使用pthread_create函数创建五个线程,可以使用pthread_mutex_t chops[5]表示有五根筷子,五个不同的临界资源,并用pthread_mutex_init(&chops[i], NULL);来初始化他们。

3.初步求解

主要思路:

void philosopher (void* arg) {
    while (1) {
        think();//思考
        hungry();//饥饿
        pthread_mutex_lock(&chopsticks[left]);//左筷子上锁
        pthread_mutex_lock(&chopsticks[right]);//右筷子上锁
        eat();//吃饭
        pthread_mutex_unlock(&chopsticks[left]);//解锁    
        pthread_mutex_unlock(&chopsticks[right]);//解锁
    }
}

说明:这个函数为一个哲学家的活动。可以将其创建为五个不同的线程代表五位不同的哲学家。每位哲学家先思考,当某位哲学家饥饿的时候,就拿起他左边的筷子,然后拿起他右边的筷子,然后进餐,然后放下他左右的筷子并进行思考。因为筷子是临界资源,所以当一位哲学家拿起他左右的筷子的时候,就会对他左右的筷子进行加锁,使其他的哲学家不能使用,当该哲学家进餐完毕后,放下了筷子,才对资源解锁,从而使其他的哲学家可以使用。
问题:在哲学家就餐问题中,一种出现死锁的情况就是,假设一开始每位哲学家都拿起其左边的筷子,然后每位哲学家又都尝试去拿起其右边的筷子,这个时候由于每根筷子都已经被占用,因此每位哲学家都不能拿起其右边的筷子,只能等待筷子被其他哲学家释放。由此五个线程都等待被其他进程唤醒,因此就陷入了死锁。
**

4.解决方案

**

4.1

分析:第一种解决死锁问题的办法就是同时只允许四位哲学家同时拿起同一边的筷子,这样就能保证一定会有一位哲学家能够拿起两根筷子完成进食并释放资源,供其他哲学家使用,从而实现永动,避免了死锁。举个最简单的例子,假定0~3号哲学家已经拿起了他左边的筷子,然后当4号哲学家企图去拿他左边的筷子的时候,将该哲学家的线程锁住,使其拿不到其左边的筷子,然后其左边的筷子就可以被3号哲学家拿到,然后3号哲学家进餐,释放筷子,然后更多的哲学家拿到筷子并进餐。
方案这里引用信号量机制,因为同时只允许有四位哲学家同时拿起左筷子,因此我们可以设置一个信号量r,使其初始值为4,然后每当一位哲学家企图去拿起他左边的筷子的时候,先对信号量做一次P操作,从而当第五位哲学家企图去拿做筷子的时候,对r做一次P操作,r = -1,由r < 0得第五位哲学家的线程被阻塞,从而不能拿起左筷子,因此也就避免了死锁问题。然后当哲学家放下他左边的筷子的时候,就对r做一次V操作。

void philosopher (void* arg) {
    while (1) {
        think();
        hungry();
        P(&r);//C语言提供的P操作的函数是sem_wait
        pthread_mutex_lock(&chopsticks[left]);
        pthread_mutex_lock(&chopsticks[right]);
        eat();
        pthread_mutex_unlock(&chopsticks[left]);
        V(&r);//C语言提供的V操作的函数是sem_post
        pthread_mutex_unlock(&chopsticks[right]);
    }
}

4.2

设置一个全局互斥量mutex,用来锁住全部的临界资源,当一个哲学家企图拿筷子的时候,就将所有的资源锁住,然后让他去拿他需要的筷子,等他取到他需要的筷子之后,就解锁,然后让其他哲学家取筷子

void philosopher (void* arg) {
    while (1) {
        think();
        hungry();
        pthread_mutex_lock(mutex);
        pthread_mutex_lock(&chopsticks[left]);
        pthread_mutex_lock(&chopsticks[right]);
        pthread_mutex_unlock(mutex);
        eat();
        pthread_mutex_unlock(&chopsticks[left]);
        pthread_mutex_unlock(&chopsticks[right]);
    }
}

4.3

第三种解决的办法就是规定奇数号哲学家先拿起他左边的筷子,然后再去拿他右边的筷子,而偶数号的哲学家则相反,这样的话总能保证一个哲学家能获得两根筷子完成进餐,从而释放其所占用的资源

void philosopher (void* arg) {
    int i = *(int *)arg;
    int left = i;
    int right = (i + 1) % N;
    while (1) {
        printf("哲学家%d正在思考问题\n", i);
        delay(50000);

        printf("哲学家%d饿了\n", i);
        if (i % 2 == 0) {//偶数哲学家,先右后左
            pthread_mutex_lock(&chopsticks[right]);
            pthread_mutex_lock(&chopsticks[left]);
            eat();
            pthread_mutex_unlock(&chopsticks[left]);
            pthread_mutex_unlock(&chopsticks[right]);
        } else {//奇数哲学家,先左后又
            pthread_mutex_lock(&chopsticks[left]);
            pthread_mutex_lock(&chopsticks[right]);
            eat();
            pthread_mutex_unlock(&chopsticks[right]);
            pthread_mutex_unlock(&chopsticks[left]);
        }
    }
}
  • 3
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是使用记录型信号量实现哲学家进餐问题解决方案的伪代码: ``` const int n = 5; // 哲学家个数 Semaphore chopsticks[n] = {1, 1, 1, 1, 1}; // 筷子信号量,初始值为1 Semaphore mutex = 1; // 互斥信号量,初始值为1 void philosopher(int i) { while (true) { think(); // 思考 if (i % 2 == 0) { // 偶数号哲学家先拿右边的筷子 wait(chopsticks[(i + 1) % n]); // 等待右边的筷子 wait(mutex); // 进入临界区 wait(chopsticks[i]); // 拿起左边的筷子 } else { // 奇数号哲学家先拿左边的筷子 wait(chopsticks[i]); // 拿起左边的筷子 wait(mutex); // 进入临界区 wait(chopsticks[(i + 1) % n]); // 等待右边的筷子 } eat(); // 进餐 signal(chopsticks[i]); // 放下左边的筷子 signal(chopsticks[(i + 1) % n]); // 放下右边的筷子 signal(mutex); // 离开临界区 } } ``` 在上面的代码中,`chopsticks` 数组表示筷子的信号量,每个筷子的初始值为1。`mutex` 表示互斥信号量,初始值为1。`philosopher` 函数表示哲学家的行为,其中 `i` 表示哲学家的编号,从0开始。在 `philosopher` 函数中,哲学家会先思考一段时间,然后尝试拿起左边或右边的筷子(根据自己是奇数号还是偶数号哲学家),如果成功拿起了一只筷子,就等待另一只筷子;如果同时拿起了两只筷子,就可以进餐。最后,哲学家会放下两只筷子,然后继续思考。在拿起和放下筷子的过程中,需要使用互斥信号量来避免竞争条件。这样,就可以避免问题的发生。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值