操作系统(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 作为一种抽象数据类型,封装了共享数据和同步操作,在解决并发问题时提供了极大的便利。

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

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

相关文章

ESP32学习笔记_FreeRTOS(1)——Task的创建和使用

摘要(From AI): 本文是基于 FreeRTOS 和 ESP_IDF 的学习笔记&#xff0c;详细讲解了任务管理、优先级设置、任务堆栈监控、看门狗定时器&#xff08;IWDT 和 TWDT&#xff09;等关键功能。内容涵盖任务创建与删除、任务挂起与恢复、时间片轮转调度机制&#xff0c;以及任务看门…

95.【C语言】数据结构之双向链表的头插,头删,查找,中间插入,中间删除和销毁函数

目录 1.双向链表的头插 方法一 方法二 2.双向链表的头删 3.双向链表的销毁 4.双向链表的某个节点的数据查找 5.双向链表的中间插入 5.双向链表的中间删除 6.对比顺序表和链表 承接94.【C语言】数据结构之双向链表的初始化,尾插,打印和尾删文章 1.双向链表的头插 方法…

【Docker容器化技术】docker安装与配置、常用命令、容器数据卷、应用部署实战、Dockerfile、服务编排docker-compose、私有仓库

文章目录 一、Docker的安装与配置1、docker概述2、安装docker3、docker架构4、配置镜像加速器 二、Docker命令1、服务相关命令2、镜像相关命令3、容器相关命令 三、Docker容器数据卷1、数据卷概念及作用2、配置数据卷3、配置数据卷容器 四、Docker应用部署实战1、部署MySQL2、部…

.netCore WebAPI中字符串加密与解密

In today’s digital landscape, securing sensitive information is more critical than ever. If you’re using ASP.NET Core, you might store configuration settings in appsettings.json. However, hardcoding sensitive data like connection strings or API keys in p…

海外云手机在出海业务中的优势有哪些?

随着互联网技术的快速发展&#xff0c;海外云手机已在出海电商、海外媒体推广和游戏行业都拥有广泛的应用。对于国内的出海电商企业来说&#xff0c;短视频引流和社交平台推广是带来有效流量的重要手段。借助云手机&#xff0c;企业能够更高效地在新兴社交平台上推广产品和品牌…

abap 可配置通用报表字段级日志监控

文章目录 1.功能需求描述1.1 功能1.2 效果展示2.数据库表解释2.1 表介绍3.数据库表及字段3.1.应用日志数据库抬头表:ZLOG_TAB_H3.2.应用日志数据库明细表:ZLOG_TAB_P3.3.应用日志维护字段配置表:ZLOG_TAB_F4.日志封装类5.代码6.调用方式代码7.调用案例程序demo1.功能需求描述 …

OceanBase 应用实践:如何处理数据空洞,降低存储空间

问题描述 某保险行业客户的核心系统&#xff0c;从Oracle 迁移到OceanBase之后&#xff0c;发现数据存储空间出现膨胀问题&#xff0c;数据空间 datasize9857715.48M&#xff0c;实际存储占用空间17790702.00M。根据 required_mb - data_mb 值判断&#xff0c;数据空洞较为严重…

React diff算法和Vue diff算法的主要区别

React和Vue都是流行的前端框架&#xff0c;它们各自实现了diff算法来优化虚拟DOM的更新过程。以下是React diff算法和Vue diff算法的主要区别&#xff1a; 1. diff策略 React diff算法&#xff1a; React的diff算法主要采用了同层级比较的策略&#xff0c;即它不会跨层级比较节…

软件测试:测试用例详解

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、通用测试用例八要素   1、用例编号&#xff1b;    2、测试项目&#xff1b;   3、测试标题&#xff1b; 4、重要级别&#xff1b;    5、预置…

C++——左值和右值的本质区别

左值和右值好干嘛&#xff1f; 深入理解左值和右值可以帮助我们对代码进行优化 一、什么是左值和右值 左值&#xff1a;有某种存储支持的变量 右值&#xff1a;临时值&#xff08;字面量、函数的结果&#xff09; Ⅰ右值是字面量 int yy 22;22本身就是一个临时的&#xf…

centos查看硬盘资源使用情况命令大全

在 CentOS 系统中&#xff0c;你可以使用几个命令来查看硬盘的资源和使用情况。以下是一些常用的命令&#xff1a; 1. df 命令 df (disk free) 用于显示文件系统的磁盘空间占用情况。 df -h-h 参数表示以人类可读的格式&#xff08;如 GB, MB&#xff09;显示。输出会显示每…

【爬虫分享】

爬虫分享 1、爬虫科普 视频发送于2024-10-27 14 _50.mp4 全屏预览下载附件 所以 爬虫 其实是非常 可“刑” 可“铐” 的。 2、逆向方法 算法还原 补环境 无头浏览器&#xff08;自动化&#xff09; rpc 参数生成速度&#xff1a;算法还原 > 补环境 > rpc > 无头…

【iOS】知乎日报第三周总结

【iOS】知乎日报第三周总结 文章目录 【iOS】知乎日报第三周总结前言评论区文字评论区的一个展开效果评论区数据的一个请求修改了主页获取数据的逻辑主页无限轮播图图片主色调的一个获取将一些拓展部分的内容写在分类里小结 前言 本周笔者因为金工实习整个项目进展比较慢&#…

OpenAI的Triton能替代Nvidia的CUDA吗

先说我的观点&#xff0c;我觉得可以&#xff0c;但是应该不是现在。 然后得补个概念&#xff0c;啥是Triton OpenAI的Triton 是一种专为高效编写深度学习运算而设计的编程语言和编译器。它旨在简化用户编写针对现代GPU&#xff08;尤其是NVIDIA GPU&#xff09;的自定义运算…

【黑马Redis原理篇】Redis数据结构

视频来源&#xff1a;原理篇[2,15] 文章目录 1.动态字符串SDS1.1 内部结构&#xff1a; 2.IntSet3.Dict3.1 dict的内部结构3.2 dict的扩容 4.ziplist压缩列表5.QuickList6.SkipList跳表7.RedisObject对象8.Redis的五种数据结构8.1 String8.2 List8.3 Set8.4 Zset 有序集合8.5 …

SpringBoot 创建多模块项目 项目分模块 项目简化 打包发布

介绍 在 Spring Boot 中&#xff0c;创建多模块项目可以帮助我们将项目拆分成多个相对独立、可重用的模块&#xff0c;从而使代码结构更清晰&#xff0c;便于管理和维护。通常&#xff0c;这样的做法可以提高开发效率&#xff0c;并且更易于进行版本控制和分布式部署。 项目结…

MySQL 数据库之表操作

1. 创建表 CREATE TABLE table_name ( field1 datatype, field2 datatype, field3 datatype ) [character set 字符集 collate 校验规则 engine 存储引擎];field 表示列名datatype 表示列的类型character set 字符集&#xff0c;如果没有指定字符集&#xff0c;则以所在数据库…

Git零基础到入门

一、开始工作区 clone: 克隆一个仓库到新的目录。 git clone https://github.com/username/repository.git init: 创建一个新的空 Git 仓库或重新初始化现有的仓库,新建git项目。 //创建项目两种方式 //一、本地项目自己创建项目&#xff0c;先创建好工作文件夹&#xff0c;通…

【R78/G15 开发板测评】串口打印 DHT11 温湿度传感器、DS18B20 温度传感器数据,LabVIEW 上位机绘制演化曲线

【R78/G15 开发板测评】串口打印 DHT11 温湿度传感器、DS18B20 温度传感器数据&#xff0c;LabVIEW 上位机绘制演化曲线 主要介绍了 R78/G15 开发板基于 Arduino IDE 环境串口打印温湿度传感器 DHT11 和温度传感器 DS18B20 传感器的数据&#xff0c;并通过LabVIEW上位机绘制演…

Chromium Mojo(IPC)进程通信演示 c++(2)

122版本自带的mojom通信例子associated-interface 仅供学习参考&#xff1a; codelabs\mojo_examples\02-associated-interface-freezing 一、目录结构如图&#xff1a; 二、interface.mojom接口 1、codelabs\mojo_examples\mojom\interface.mojom // Copyright 2023 The C…