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

news2026/2/11 14:27:17

目录

一、概念

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

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

四、使用管程来解决死锁


一、概念

问题描述:

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

约束条件:

(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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2267976.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Three.js 字体

在 Three.js 中&#xff0c;我们可以通过 FontLoader 加载字体&#xff0c;并结合 TextGeometry 创建 3D 文本。加载字体是因为字体文件包含了字体的几何信息&#xff0c;例如字体的形状、大小、粗细等&#xff0c;而 TextGeometry 则是根据字体信息生成 3D 文本的几何体。 在…

机器人C++开源库The Robotics Library (RL)使用手册(三)

进入VS工程,我们先看看这些功能函数及其依赖库的分布关系: rl命名空间下,主要有八大模块。 搞定VS后将逐个拆解。 1、编译运行 根据报错提示,配置相应错误的库(根据每个人安装位置不同而不同,我的路径如下:) 编译所有,Release版本耗时大约10分钟。 以rlPlan运动…

【GUI-PyQt5】简介

1. 简介 GUI&#xff1a;带图形的用户接口程序&#xff0c;也就是桌面应用。 2. 分类 2.1 基本窗口控件 QMainWindowQwidgetQlabelQLineEdit菜单工具栏 2.2 高级组件 QTableViewQListView容器多线程 2.3 布局管理 QBoxLayoutQGridLayoutQFormLayout嵌套布局 2.4 信号与…

Mysql学习笔记之SQL-4

这篇文章开始介绍SQL语句的最后一个部分&#xff0c;DCL&#xff08;Data Control Language&#xff09;数据库控制语言。 1.简介 DCL英文全称是Data Control Language(数据控制语言)&#xff0c;用来管理数据库用户、控制数据库的访 问权限。 这一部分比较简单&#xff0c;主…

Chrome被360导航篡改了怎么改回来?

一、Chrome被360导航篡改了怎么改回来&#xff1f; 查看是否被360主页锁定&#xff0c;地址栏输入chrome://version&#xff0c;看命令行end后面&#xff08;蓝色部分&#xff09;&#xff0c;是否有https://hao.360.com/?srclm&lsn31c42a959f 修改步骤 第一步&#xff1a…

STM32-笔记18-呼吸灯

1、实验目的 使用定时器 4 通道 3 生成 PWM 波控制 LED1 &#xff0c;实现呼吸灯效果。 频率&#xff1a;2kHz&#xff0c;PSC71&#xff0c;ARR499 利用定时器溢出公式 周期等于频率的倒数。故Tout 1/2KHZ&#xff1b;Ft 72MHZ PSC71&#xff08;喜欢设置成Ft的倍数&…

内部类(2)

大家还&#xff0c;今天我们继续来学习内部类的知识&#xff0c;今天我们来看看其余几种内部类类型&#xff0c;那么话不多说 我们直接开始。 注&#xff1a;它是一个static的一个常量 一旦初始化就不能够进行修改了. 注:1.一般情况下我们定义常量的时候,会定成大写的: 2.a不…

Go Energy 跨平台框架 v2.5.1 发布

Energy 框架 是Go语言基于CEF 和 LCL 开发的跨平台 GUI 框架, 具体丰富的系统原生 UI 控件集, 丰富的 CEF 功能 API&#xff0c;简化且不失功能的 CEF 功能 API 使用。 特性&#xff1f; 特性描述跨平台支持 Windows, macOS, Linux简单Go语言的简单特性&#xff0c;使用简单…

欧科云链OKLink:比特币与以太坊“双重启动”将如何撬动市场?

近日&#xff0c;OKLink 与 137Labs 联合举办 X Space&#xff0c;围绕宏观经济环境、政策及机构投资的影响等话题&#xff0c;分享如何把握 Web3 中的潜在机会与辨别风险。OKG Research 首席研究员 Hedy、BuilderRocket Accelerator 研究合伙人 Vivienna、VC 分析员 Bunny、BU…

探索仓颉编程语言:功能、实战与展望

目录 引言 一.使用体验 二.功能剖析 1.丰富的数据类型与控制结构 2.强大的编程范式支持 3.标准库与模块系统 4.并发编程能力 三.实战案例 1.项目背景与目标 2.具体实现步骤 (1).导入必要的模块 (2).发送 HTTP 请求获取网页内容 (3).解析 HTML 页面提取文章信息 (…

JavaFX FXML模式下的布局

常见布局方式概述 在 JavaFX FXML 模式下&#xff0c;有多种布局方式可供选择。这些布局方式可以帮助您有效地组织和排列 UI 组件&#xff0c;以创建出美观且功能良好的用户界面。常用布局容器及布局方式 BorderPane 布局 特点&#xff1a;BorderPane 将空间划分为五个区域&…

OpenFeign介绍以及使用

介绍 OpenFeign 是一个声明式的 Web 服务客户端&#xff0c;用于简化在 Java 应用中调用 HTTP API 的过程&#xff0c;在 Spring Cloud 体系里被广泛应用&#xff0c;它有以下关键特性&#xff1a; 声明式调用&#xff1a;基于注解&#xff0c;开发人员只需定义接口并添加注解…

李永乐线性代数:A可逆,AX=B相关推论和例题解题思路

例题1&#xff1a; 思路讲解&#xff1a; 这个 (A-2E)可逆,所以有P(A-2E) E&#xff0c; 也就是(A-2E)的逆矩阵是P&#xff1b; 那么PA (A-2E)的逆 * A B P(A-2E,A)(E,B) 所以就可以直接求出B&#xff0c;也就是(A-2E)的逆 * A 例题2&#xff1a; 思路讲解&#xff1a;…

【Compose multiplatform教程18】多平台资源的设置和配置

要正确配置项目以使用多平台资源&#xff0c;请执行以下操作&#xff1a; 添加库依赖项。 为每种资源创建必要的目录。 为限定资源创建其他目录&#xff08;例如&#xff0c;深色 UI 主题或本地化字符串的不同图像&#xff09;。 依赖项和目录设置 要访问多平台项目中的资源…

Doris的SQL原理解析

今天来介绍下Doris的SQL原理解析&#xff0c;主要从语法、解析、分析、执行等几个方面来介绍&#xff0c;可以帮助大家对Doris底层有个清晰的理解~ 一、Doris简介 Apache Doris是一个基于MPP架构的高性能、实时的分析型数据库&#xff0c;能够较好的满足报表分析、即席查询、…

Excel for Finance 07 `FV PV` 函数

Excel 的 FV 函数用于计算一笔投资在未来的价值&#xff0c;基于固定的利率和定期付款。这是一个金融函数&#xff0c;常用来分析储蓄计划、贷款、或投资的增长。 语法&#xff1a; FV(rate, nper, pmt, [pv], [type])参数说明&#xff1a; rate&#xff08;必需&#xff09;&…

【运维】部署Gitea

部署Gitea Gitea文档 系统&#xff1a;Ubuntu 20.04.6 LTS 步骤&#xff1a; 准备数据库 使用内置 SQLite&#xff0c;无需额外准备。 下载安装 下载最新版本的 Gitea 并安装&#xff1a; wget -O gitea https://dl.gitea.com/gitea/version/gitea-version-linux-amd64 chm…

Redis KEYS查询大批量数据替代方案(推荐SCAN 命令)

文章目录 前言KEYS命令问题背景替代方案1.使用 SCAN 命令2. 使用有序集合&#xff08;Sorted Set&#xff09;3. 使用哈希&#xff08;Hash&#xff09;4. 使用 Redis 模块&#xff08;如 RediSearch&#xff09; 总结 前言 在使用 Redis 时&#xff0c;KEYS 命令虽然简单直接…

Apache Doris 创始人:何为“现代化”的数据仓库?

在 12 月 14 日的 Doris Summit Asia 2024 上&#xff0c;Apache Doris 创始人 & PMC 成员马如悦在开场演讲中&#xff0c;围绕“现代化数据仓库”这一主题&#xff0c;指出 3.0 版本是 Apache Doris 研发路程中的重要里程碑&#xff0c;他将这一进展总结为“实时之路”、“…

3. 指针、数组

目录 一、指针和数组 &#x1f350; 数组名指向首地址 &#x1f34a; 例子 二、数组作为函数参数 &#x1f34b; 数组名作为函数参数&#xff0c;为什么必须传递数组大小&#xff1f; 三、指针和字符数组 &#x1f34c;怎么样存储一个string&#xff1f; &#x1f349…