哲学家就餐问题

1 .问题描述

由Dijkstra提出并解决的哲学家就餐问题是典型的同步问题。该问题描述的是五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五只筷子,他们的生活方式是交替的进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。进餐完毕,放下筷子继续思考。

2.解题思路

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

同时,有五根筷子,每根筷子只对其相邻的两位哲学家是共享的,因此这五根筷子可以看做是五种不同的临界资源(不是一种资源有5个,因为每根筷子只能被固定编号的哲学家使用)。并对五根筷子分别编号为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]);
    }
}

这个函数代表的是一个哲学家的活动,可以将其创建为五个不同的线程代表五位不同的哲学家。每位哲学家先思考,当某位哲学家饥饿的时候,就拿起他左边的筷子,然后拿起他右边的筷子,然后进餐,然后放下他左右的筷子并进行思考。因为筷子是临界资源,所以当一位哲学家拿起他左右的筷子的时候,就会对他左右的筷子进行加锁,使其他的哲学家不能使用,当该哲学家进餐完毕后,放下了筷子,才对资源解锁,从而使其他的哲学家可以使用。

此算法可以保证不会有相邻的两位哲学家同时进餐。

若五位哲学家同时饥饿而各自拿起了左边的筷子,这使五个信号量 chopstick 均为 0,当他们试图去拿起右边的筷子时,都将因无筷子而无限期地等待下去,即可能会引起死锁。

4 死锁问题解决

解决死锁问题的办法有很多,下面对各种办法做一下详细的介绍:

4.1 Plan A

至多只允许四位哲学家同时去拿左筷子,最终能保证至少有一位哲学家能进餐,并在用完后释放两只筷子供他人使用。

设置一个初值为 4 的信号量 r,只允许 4 个哲学家同时去拿左筷子,这样就能保证至少有一个哲学家可以就餐,不会出现饿死和死锁的现象。

原理:至多只允许四个哲学家同时进餐,以保证至少有一个哲学家能够进餐,最终总会释放出他所使用过的两支筷子,从而可使更多的哲学家进餐。

因为同时只允许有四位哲学家同时拿起左筷子,因此我们可以设置一个信号量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 Plan B

仅当哲学家的左右手筷子都拿起时才允许进餐。

解法 1:利用 AND 型信号量机制实现。

原理:多个临界资源,要么全部分配,要么一个都不分配,因此不会出现死锁的情形。

void philosopher (void* arg) {
    while (1) {
        think();
        hungry();
        Swait(chopsticks[left], chopsticks[right]);
        eat();
        Spost(chopsticks[left], chopsticks[right]);
    }
}

解法 2:利用信号量的保护机制实现。

原理:通过互斥信号量 mutex 对 eat() 之前取左侧和右侧筷子的操作进行保护,可以防止死锁的出现。

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 Plan C

规定奇数号哲学家先拿左筷子再拿右筷子,而偶数号哲学家相反。

原理:按照下图,将是 2,3 号哲学家竞争 3 号筷子,4,5 号哲学家竞争 5 号筷子。1 号哲学家不需要竞争。最后总会有一个哲学家能获得两支筷子而进餐。

代码如下:

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]);
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值