操作系统(12) (并发(3)------哲学家进餐问题(Dining Philosophers Problem)解决方案/管程(monitor))

目录

哲学家进餐问题描述

解决方案 1:

解决方案 2:信号量实现

解决方案 3:使用 Monitor 的实现

1. 监视器的组成部分

2. 监视器的优点

3. 使用监视器解决哲学家进餐问题

4. 使用监视器的优势

5. 监视器的局限性

6. Mesa风格和Hoare风格监视器的区别

1. 调用 notify() 时的行为

2. Mesa风格的监视器

3. Hoare风格的监视器

总结

总结


并发控制是操作系统和多线程编程中的一个核心问题。哲学家进餐问题(Dining Philosophers Problem) 是一个经典案例,展示了在共享资源上进行协调的挑战及其解决方案。本篇博客将详细讲解哲学家进餐问题,并展示三种主要解决方案,特别是通过 Monitor 来实现高效的并发控制。

哲学家进餐问题描述

假设有五位哲学家围坐在圆桌旁,每位哲学家之间有一只叉子。哲学家有两种行为:思考和进餐。要进餐,哲学家需要同时拿起左边和右边的两只叉子。一旦进餐完毕,哲学家会放下叉子并开始思考。这个问题的核心挑战是如何设计一种机制,避免死锁和饥饿,并最大限度地提高并行度。

解决方案 1:

在这种方法中,每个哲学家都尝试同时拿起两只叉子:

void pickup(int i) {
    wait(fork[i]);          // 拿起左边的叉子
    wait(fork[(i + 1) % 5]); // 拿起右边的叉子
}

void putdown(int i) {
    signal(fork[i]);        // 放下左边的叉子
    signal(fork[(i + 1) % 5]); // 放下右边的叉子
}

缺点:这种实现容易导致死锁。例如,如果所有哲学家同时拿起左边的叉子并等待右边的叉子,整个系统将陷入僵局。

解决方案 2:信号量实现

使用信号量控制叉子的可用性和哲学家的同步。

sem_t forks[5]; // 初始化为1,表示每个叉子都可用

void pickup(int i) {
    sem_wait(&forks[i]);          // 拿起左边的叉子
    sem_wait(&forks[(i + 1) % 5]); // 拿起右边的叉子
}

void putdown(int i) {
    sem_post(&forks[i]);          // 放下左边的叉子
    sem_post(&forks[(i + 1) % 5]); // 放下右边的叉子
}

缺点:这种方法通过信号量避免了死锁,但无法实现最大并行度,因为哲学家无法同时进餐。

解决方案 3:使用 Monitor 的实现

1. 监视器的组成部分

监视器是一种高级同步机制,通过封装共享资源和相关操作,确保只有一个线程能在任一时刻访问和操作共享资源。其主要组成部分包括:

  • 共享私有数据(Shared Private Data):管理监视器所保护的资源,外部线程无法直接访问,确保数据封装性和安全性。
  • 操作数据的程序(Monitor Procedures):访问和操作共享数据的唯一途径,保证线程在同步环境下安全地进行数据操作。
  • 同步原语(Synchronization Primitives):使用条件变量和锁来管理线程的等待和唤醒,避免繁忙等待,提高系统资源利用率。

2. 监视器的优点

  • 避免信号量的复杂性:监视器自动封装了同步逻辑,简化了代码的实现,减少了使用信号量引发的死锁风险。
  • 自动管理互斥:监视器保证在任意时刻,只有一个线程可以进入和执行监视器中的方法。
  • 避免繁忙等待:当线程无法获取资源时,进入等待队列而不占用 CPU 资源。

3. 使用监视器解决哲学家进餐问题

在哲学家进餐问题中,使用监视器可提供一种简洁而有效的资源竞争与同步管理方法。

核心思路

  • 状态管理:每个哲学家有三种状态——THINKING(思考)、HUNGRY(饥饿)、EATING(用餐),并通过一个共享的 state 数组记录状态。
  • 条件变量:每个哲学家都有自己的条件变量 self[i],用于在不能用餐时进入等待状态。
  • 互斥和同步:使用锁和条件变量确保同时只有一个线程能操作监视器,避免竞争条件。

实现逻辑

  1. pickup(i)

    • 哲学家 i 调用 pickup() 将其状态设置为 HUNGRY。
    • 调用 test(i) 检查左右邻居状态,决定是否进入用餐状态。
    • 如果不能用餐,进入 self[i] 等待队列,等待唤醒。
  2. putdown(i)

    • 用餐结束后,调用 putdown() 将状态设置为 THINKING。
    • 调用 test() 检查左右邻居是否可以用餐,并唤醒满足条件的哲学家。
  3. test(i)

    • 检查哲学家 i 是否可以用餐,即 state[i] == HUNGRY 且左右邻居都不在 EATING 状态。
    • 条件满足时,将状态设为 EATING 并唤醒 self[i]

代码示例

monitor DiningPhilosophers {
    enum { THINKING, HUNGRY, EATING } state[5];
    condition self[5];

    void pickup(int i) {
        state[i] = HUNGRY;
        test(i);
        if (state[i] != EATING)
            self[i].wait();
    }

    void putdown(int i) {
        state[i] = THINKING;
        test((i + 4) % 5);
        test((i + 1) % 5);
    }

    void test(int i) {
        if (state[i] == HUNGRY &&
            state[(i + 4) % 5] != EATING &&
            state[(i + 1) % 5] != EATING) {
            state[i] = EATING;
            self[i].signal();
        }
    }

    initialization_code() {
        for (int i = 0; i < 5; i++)
            state[i] = THINKING;
    }
}

4. 使用监视器的优势

  • 无死锁:通过 self[i] 条件变量控制,每个哲学家在无法用餐时主动等待,避免了相互持有资源而导致的死锁。
  • 高并发性:允许不相邻的哲学家同时用餐,实现最大化并行。
  • 代码简洁:使用监视器封装同步逻辑,简化了实现和维护。

5. 监视器的局限性

  • 嵌套监视器:在一个监视器内调用另一个监视器方法可能导致死锁。这是因为嵌套结构会增加持有锁的复杂度,导致不同的线程之间出现相互等待,最终无法继续执行。
  • 优先级反转:监视器无法解决优先级反转问题。当一个低优先级线程持有监视器时,高优先级线程可能会被阻塞,直到低优先级线程释放监视器。
  • 灵活性受限:相比信号量,监视器在某些复杂同步需求中不够灵活。

6. Mesa风格和Hoare风格监视器的区别

1. 调用 notify() 时的行为
  • 没有等待线程
    • 通知线程继续执行,信号会被丢失,不会唤醒任何线程。
  • 有等待线程
    • 唤醒一个线程执行,其他线程继续等待。
2. Mesa风格的监视器
  • 通知线程保持锁并继续执行,等待线程需等待锁释放后才能继续。
  • 优点:实现简单,适合多任务调度。
  • 缺点:等待线程可能需延迟较长时间。
3. Hoare风格的监视器
  • 通知线程立即释放锁,唤醒等待线程并将锁交给它。
  • 等待线程完成后,将锁归还给通知线程。
  • 优点:更快响应信号,提供更精确的同步控制。
  • 缺点:实现复杂,锁的频繁切换增加了系统开销。
总结
  • Mesa风格:灵活、易于实现,适合多任务调度的操作系统。
  • Hoare风格:同步更强,但实现复杂,常用于理论讨论。

总结

哲学家进餐问题展示了并发编程中的同步挑战。简单的实现容易导致死锁,而使用信号量可以避免死锁但并行度有限。通过 Monitor 实现,解决了死锁问题并最大化了并行度,是一种高级而高效的并发控制方法。

Monitor 作为一种抽象数据类型,封装了共享数据和同步操作,在解决并发问题时提供了极大的便利。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

“心玲守护”乡村孩子的一片天公益活动在10所学校开展

2023年9月—2024年10月期间&#xff0c;由林志玲女士发起、中国乡村发展基金会支持&#xff0c;并联合重庆市渝中区红樱桃义工协会执行的“心玲守护”乡村孩子的一片天——儿童青少年心理健康援助项目活动&#xff0c;已在重庆市万州区、璧山区、巫山县和湖南省益阳市区域内的1…

计算机网络——1.1计算机网络概述

计算机网络——计算机网络概念 前言 计算机网络是计算机学习中必不可少的一环&#xff0c;甚至可以说&#xff0c;是离我们普通人日常生活最近的计算机知识。为什么呢&#xff1f;因为我们上网上网&#xff0c;都离不开计算机网络&#xff0c;打游戏&#xff0c;刷剧&#xff…

使用HtmlAgilityPack+PuppeteerSharp+iText7抓取IdentityServer4帮助文档

需要学习IdentityServer4的用法&#xff0c;但是在IdentityServer4帮助文档网站&#xff08;参考文献1&#xff09;中没有找到下载离线文档的地方&#xff0c;准备使用HtmlAgilityPackPuppeteerSharpiText7将网站内容抓取生成离线PDF文档&#xff0c;便于本机学习、查看。   …

热烈庆祝,2024年11月9日(星期六)骑行马刺沟顺利结束

晨光微露&#xff1a;蓄势待发清晨的第一缕阳光穿透薄雾&#xff0c;照亮了集合现场。我们校长群的骑行爱好者们早早地聚集在约定地点&#xff0c;检查装备、调整车辆&#xff0c;彼此间寒暄着&#xff0c;兴奋之情溢于言表。随着一声令下&#xff0c;队伍正式出发&#xff0c;…

python数据分析|二 IPython和JupyterNotebooks

一 python 解释器 Python解释器同一时间只能运行一个程序的一条语句。 如何适用&#xff1a; win r cmd 要退出Python解释器返回终端&#xff0c;可以输入 exit() 或 Ctrl-D。 假设创建了一个 hello_world.py 文件&#xff0c;它的内容是&#xff1a; 可以用下面的命令运…

【持续更新】【NLP项目】【自然语言处理】智能聊天机器人——“有问必答”【Chatbot】第2章、《模式一:问候模式》

智能聊天机器人——“有问必答” 【注】该项目已开源&#xff0c;开源地址为&#xff1a;链接&#xff0c;代码更新可能不及时。 第2章、《模式一&#xff1a;问候模式》 主窗体的布局如下图所示&#xff1a; 共九种功能模式&#xff0c;最下方为关闭窗口按钮。 点击问候模…

@RestController 源码解读:解决 Web 开发中 REST 服务的疑难杂症

目录 一、RestContrller注解 1.1 查看底层源码 1.2 AliasFor注解说明 1.2.1 注解别名 1.2.2 元数据别名 1.3 value() 方法的作用 一、RestContrller注解 1.1 查看底层源码 首先编写如下内容&#xff1a; RestController public class TestController {} 按住 Ctrl &am…

【Android】轮播图——Banner

引言 Banner轮播图是一种在网页和移动应用界面设计中常见的元素&#xff0c;主要用于在一个固定的区域内自动或手动切换一系列图片&#xff0c;以展示不同的内容或信息。这个控件在软件当中经常看到&#xff0c;商品促销、热门歌单、头像新闻等等。它不同于ViewPgaer在于无需手…

游戏引擎学习第一天

视频参考: https://www.bilibili.com/video/BV1zGDCYHErA/ 创建一个保存项目的路径 VS的安装略过&#xff0c;个人自行百度 1. vs 创建第一个CMAKE的窗口项目 game.cpp 修改如下的代码 到https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-winmain 去…

ArcGIS软件之“计算面积几何”地图制作

目录 一、消防站的泰森多边形ex12二、人口调查的泰森多边形三、人口调查的泰森多边形属性设置四、计算面积几何&#xff0c;用于求密度五、求密度六、给“现有中学”属性 R1赋值七、“现有中学”设置多环缓存区 并为它赋值八、“土地使用”为不同的功能区赋值九、三个图层相交十…

Rust @绑定(Rust@绑定)(在模式匹配的同时将值绑定到变量)

文章目录 Rust中的绑定基础概念示例&#xff1a;基本模式匹配 绑定的使用示例&#xff1a;范围匹配并绑定变量 深入探索绑定的好处示例&#xff1a;复杂数据结构中的应用 总结 附加 Rust中的绑定 Rust 语言以其强类型系统和内存安全的特性著称。在进行模式匹配时&#xff0c;R…

使用EasyExcel实现导出excel文件时生成多级下拉选

前言 公司有个需求本来只涉及到两个下拉选项&#xff0c;后面就想能不能实现多个下拉选&#xff0c;当然我这里说的多个下拉选是联动的&#xff0c;比如省、地市、区县这种。 实现步骤 1、添加EasyExcel的Maven依赖 <dependency><groupId>com.alibaba</group…

海量小文件挑战下的CephFS:优化策略与实践探索

文章目录 1.背景2.基本概念2.1 CephFS IO流程2.2 Ceph-FUSE 3. 问题3.1 问题源起3.2 理论分析3.3 原因排查3.3.1 Ceph-FUSE日志分析3.3.2 提出猜想3.3.3 代码验证3.3.3.1 MDS端3.3.3.2 Ceph-FUSE端 3.4 小结 1.背景 随着大数据、人工智能技术的蓬勃发展&#xff0c;人类对于算…

编写一个脚本实现参数的远程主机网络探测python test_ip.py 192.168.0.10~192.168.0.100(sys模块)

""" 编写一个脚本实现参数的远程主机网络探测python test_ip.py 192.168.0.10~192.168.0.100 """ #导入模块 #读取起始IP&#xff0c;结束IP import sys start_ip sys.argv[1] end_ip sys.argv[2] # print(start_ip,end_ip)##########组装数据…

lvgl: 示例入门

目录 1. A very simple hello world label 2. A button with a label and react on click event 3. Create styles from scratch for buttons 4. Create a slider and write its value on a label 1. A very simple hello world label void _lv_example_get_started_1(void) …

Redis2:Redis数据结构介绍、通用命令、String类型、Key的层级格式

欢迎来到“雪碧聊技术”CSDN博客&#xff01; 在这里&#xff0c;您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者&#xff0c;还是具有一定经验的开发者&#xff0c;相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导&#xff0c;我将…

云计算基础

声明 学习视频来自B站UP主泷羽sec,如涉及侵权马上删除文章 笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负 目录 一、云架构介绍 二、云服务 三、云分类 四、共享责任模型 五、云架构 六、云架构设计 七、集…

【超级详细】基于Zynq FPGA对雷龙SD NAND的测试

目录 一、SD NAND特征1.1 SD卡简介1.2 SD卡Block图 二、SD卡样片三、Zynq测试平台搭建3.1 测试流程3.2 SOC搭建 一、SD NAND特征 1.1 SD卡简介 雷龙的SD NAND有很多型号&#xff0c;在测试中使用的是CSNP4GCR01-AMW与CSNP32GCR01-AOW。芯片是基于NAND FLASH和 SD控制器实现的…

python中常见的8种数据结构之一列表

列表是Python中最常见的数据结构之一。它是一种有序的集合&#xff0c;可以包含不同类型的数据。 以下是列表的一些特点和常见操作&#xff1a; 1. 定义列表&#xff1a;可以使用方括号&#xff08;[]&#xff09;来定义一个空列表&#xff0c;也可以在方括号中添加元素来初始…

Python 在PDF中绘制形状(线条、矩形、椭圆形等)

在PDF中绘制图形可以增强文档的视觉效果。通过添加不同类型的形状&#xff0c;如实线、虚线、矩形、圆形等&#xff0c;可以使文档更加生动有趣&#xff0c;提高读者的阅读兴趣。这对于制作报告、演示文稿或是教材特别有用。本文将通过以下几个示例介绍如何使用Python 在PDF中绘…