【操作系统】哲学家进餐问题

目录

一、概念

二、以原子的思想解决死锁

 三、破环环路的思想解决死锁

四、使用管程来解决死锁


一、概念

问题描述:

有五个哲学家,他们的生活方式是交替地进行思考和进餐,哲学家们共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五支筷子,平时哲学家进行思考,饥饿时便试图取其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐,该哲学家进餐完毕后,放下左右两只筷子又继续思考。

约束条件:

(1)只有拿到两只筷子时,哲学家才能吃饭。

(2)如果筷子已被别人拿走,则必须等别人吃完之后才能拿到筷子。

(3)任一哲学家在自己未拿到两只筷子吃完饭前,不会放下手中已经拿到的筷子。

 筷子是临界资源,一段时间只允许一位哲学家使用。为了表示互斥,用一个互斥锁表示一只筷子,五个互斥锁构成互斥锁数组。

进餐毕,先放下他左边的筷子,然后再放下右边的筷子。当五个哲学家同时去取他左边的筷子,每人拿到一只筷子且不释放,即五个哲学家只得无限等待下去,引起死锁。

以下代码可能引起死锁:

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <chrono>

const int NUM_PHILOSOPHERS = 5;

std::vector<std::mutex> forks(NUM_PHILOSOPHERS);

void philosopher(int id) {
    //1 a 1 a 1 a 1 a 1 a
    int left_fork = id;
    int right_fork = (id + 1) % NUM_PHILOSOPHERS;

    while (true) {
        // 思考
        std::cout << "Philosopher " << id << " is thinking." << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));

        // 尝试拿起筷子
        std::unique_lock<std::mutex> left_lock(forks[left_fork]);
        std::unique_lock<std::mutex> right_lock(forks[right_fork]);

        // 就餐
        std::cout << "Philosopher " << id << " is eating." << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));

        // 放下筷子
        right_lock.unlock();
        left_lock.unlock();
    }
}

int main() {
    std::vector<std::thread> philosophers;

    for (int i = 0; i < NUM_PHILOSOPHERS; ++i) {
        philosophers.emplace_back(philosopher, i);
    }

    for (auto& ph : philosophers) {
        ph.join();
    }

    return 0;
}

二、以原子的思想解决死锁

原子操作指的是一组不可分割的操作,这些操作要么全部执行成功,要么全部不执行,是一个整体,不可再分。

若只拿到左筷子,没有拿到右筷子,则rollback,释放所以的左筷子锁。

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <chrono>

const int NUM_PHILOSOPHERS = 5;

std::vector<std::mutex> forks(NUM_PHILOSOPHERS);
void philosopher(int id) {
    int left_fork = id;
    int right_fork = (id + 1) % NUM_PHILOSOPHERS;

    while (true) {
        // 思考
        std::cout << "Philosopher " << id << " is thinking." << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));

        // 尝试同时拿起两根筷子
        while (true) {
            // 尝试锁定左边的筷子
            std::unique_lock<std::mutex> left_lock(forks[left_fork], std::try_to_lock);
            if (left_lock.owns_lock()) {
                // 尝试锁定右边的筷子
                std::unique_lock<std::mutex> right_lock(forks[right_fork], std::try_to_lock);
                if (right_lock.owns_lock()) {
                    // 成功锁定两根筷子,开始就餐
                    std::cout << "Philosopher " << id << " is eating." << std::endl;
                    std::this_thread::sleep_for(std::chrono::milliseconds(1000));

                    // 放下筷子(自动释放锁)
                    break;
                } else {
                    // 未能锁定右边的筷子,释放左边的筷子
                    left_lock.unlock();
                }
            }

            // 等待一段时间后重试
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
    }
}
int main() {
    std::vector<std::thread> philosophers;

    for (int i = 0; i < NUM_PHILOSOPHERS; ++i) {
        philosophers.emplace_back(philosopher, i);
    }

    for (auto& ph : philosophers) {
        ph.join();
    }

    return 0;
}

 三、破环环路的思想解决死锁

奇数号哲学家先拿左边的筷子,偶数号哲学家先拿右边的筷子,可以破坏循环等待的条件,从而避免死锁。这种方法的核心思想是打破哲学家之间的对称性,使得不会所有哲学家同时持有左边的筷子并等待右边的筷子。

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <chrono>

const int NUM_PHILOSOPHERS = 5;

std::vector<std::mutex> forks(NUM_PHILOSOPHERS); // 每根筷子用一个互斥锁表示

void philosopher(int id) {
    int left_fork = id;
    int right_fork = (id + 1) % NUM_PHILOSOPHERS;

    while (true) {
        // 思考
        std::cout << "Philosopher " << id << " is thinking." << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));

        // 奇数号哲学家先拿左边的筷子,偶数号哲学家先拿右边的筷子
        if (id % 2 == 1) {
            // 奇数号哲学家
            std::unique_lock<std::mutex> left_lock(forks[left_fork]);
            std::unique_lock<std::mutex> right_lock(forks[right_fork]);

            // 就餐
            std::cout << "Philosopher " << id << " is eating." << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));

            // 放下筷子(自动释放锁)
        } else {
            // 偶数号哲学家
            std::unique_lock<std::mutex> right_lock(forks[right_fork]);
            std::unique_lock<std::mutex> left_lock(forks[left_fork]);

            // 就餐
            std::cout << "Philosopher " << id << " is eating." << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));

            // 放下筷子(自动释放锁)
        }
    }
}

int main() {
    std::vector<std::thread> philosophers;

    // 创建哲学家线程
    for (int i = 0; i < NUM_PHILOSOPHERS; ++i) {
        philosophers.emplace_back(philosopher, i);
    }

    // 等待所有哲学家线程完成(实际上永远不会完成)
    for (auto& ph : philosophers) {
        ph.join();
    }

    return 0;
}

限制同时就餐的哲学家数量,破坏环路,可以确保至少有一位哲学家能够成功进餐,从而避免死锁。这种方法的核心思想是 减少资源竞争,确保系统中始终有可用的资源。 

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <chrono>
#include <semaphore.h>

const int NUM_PHILOSOPHERS = 5;

std::vector<std::mutex> forks(NUM_PHILOSOPHERS); // 每根筷子用一个互斥锁表示
sem_t table; // 信号量,限制同时就餐的哲学家数量

void philosopher(int id) {
    int left_fork = id;
    int right_fork = (id + 1) % NUM_PHILOSOPHERS;

    while (true) {
        // 思考
        std::cout << "Philosopher " << id << " is thinking." << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));

        // 尝试进入就餐状态
        sem_wait(&table);

        // 拿起左边的筷子
        std::unique_lock<std::mutex> left_lock(forks[left_fork]);
        std::cout << "Philosopher " << id << " picked up left fork " << left_fork << "." << std::endl;

        // 拿起右边的筷子
        std::unique_lock<std::mutex> right_lock(forks[right_fork]);
        std::cout << "Philosopher " << id << " picked up right fork " << right_fork << "." << std::endl;

        // 就餐
        std::cout << "Philosopher " << id << " is eating." << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));

        // 放下右边的筷子
        right_lock.unlock();
        std::cout << "Philosopher " << id << " put down right fork " << right_fork << "." << std::endl;

        // 放下左边的筷子
        left_lock.unlock();
        std::cout << "Philosopher " << id << " put down left fork " << left_fork << "." << std::endl;

        // 离开就餐状态
        sem_post(&table);
    }
}

int main() {
    // 初始化信号量,最多允许 4 位哲学家同时就餐
    sem_init(&table, 0, NUM_PHILOSOPHERS - 1);

    std::vector<std::thread> philosophers;

    // 创建哲学家线程
    for (int i = 0; i < NUM_PHILOSOPHERS; ++i) {
        philosophers.emplace_back(philosopher, i);
    }

    // 等待所有哲学家线程完成(实际上永远不会完成)
    for (auto& ph : philosophers) {
        ph.join();
    }

    // 销毁信号量
    sem_destroy(&table);

    return 0;
}

四、使用管程来解决死锁

程是一种将共享资源及其操作封装在一起的同步机制,它通过条件变量和互斥锁实现线程间的同步和互斥。

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <chrono>
#include <condition_variable>

const int NUM_PHILOSOPHERS = 5;

class DiningPhilosophers {
private:
    std::vector<std::mutex> forks; // 每根筷子用一个互斥锁表示
    std::vector<std::condition_variable> conditions; // 每个哲学家的条件变量
    std::vector<bool> isEating; // 记录哲学家是否正在就餐
    std::mutex monitorMutex; // 管程的互斥锁

public:
    DiningPhilosophers() : forks(NUM_PHILOSOPHERS), conditions(NUM_PHILOSOPHERS), isEating(NUM_PHILOSOPHERS, false) {}

    // 哲学家请求筷子
    void requestForks(int id) {
        std::unique_lock<std::mutex> lock(monitorMutex);

        int left_fork = id;
        int right_fork = (id + 1) % NUM_PHILOSOPHERS;

        // 如果左右筷子被占用,则等待
        while (isEating[left_fork] || isEating[right_fork]) {
            conditions[id].wait(lock);
        }

        // 拿起筷子
        forks[left_fork].lock();
        forks[right_fork].lock();
        isEating[id] = true;

        std::cout << "Philosopher " << id << " picked up forks " << left_fork << " and " << right_fork << "." << std::endl;
    }

    // 哲学家释放筷子
    void releaseForks(int id) {
        std::unique_lock<std::mutex> lock(monitorMutex);

        int left_fork = id;
        int right_fork = (id + 1) % NUM_PHILOSOPHERS;

        // 放下筷子
        forks[left_fork].unlock();
        forks[right_fork].unlock();
        isEating[id] = false;

        std::cout << "Philosopher " << id << " put down forks " << left_fork << " and " << right_fork << "." << std::endl;

        // 通知左右哲学家可以尝试拿筷子
        conditions[left_fork].notify_all();
        conditions[right_fork].notify_all();
    }
};

void philosopher(int id, DiningPhilosophers& dining) {
    while (true) {
        // 思考
        std::cout << "Philosopher " << id << " is thinking." << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));

        // 请求筷子
        dining.requestForks(id);

        // 就餐
        std::cout << "Philosopher " << id << " is eating." << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));

        // 释放筷子
        dining.releaseForks(id);
    }
}

int main() {
    DiningPhilosophers dining;
    std::vector<std::thread> philosophers;

    // 创建哲学家线程
    for (int i = 0; i < NUM_PHILOSOPHERS; ++i) {
        philosophers.emplace_back(philosopher, i, std::ref(dining));
    }

    // 等待所有哲学家线程完成(实际上永远不会完成)
    for (auto& ph : philosophers) {
        ph.join();
    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值