当前课程知识点:操作系统 > 第十八讲 信号量与管程 > 18.3 管程 > 18.3 管程
下面我们来介绍管程
这是我们这里
今天说到的第二种同步方法
我们在刚才的
同步方法介绍当中呢
有了信号量
它使得我们在操作系统参与下
我们处理临界区呢
比原来会变得更方便
好 那接下来我们介绍的管程呢
实际上是想改进信号量
在处理临界区的时候一些麻烦
比如说在我们刚才看到的
生产者消费者问题当中
信号量的PV操作
是分散在生产者和消费者
两个不同的进程当中
在这种情况下
我们PV操作的配对
是比较困难的
好 我们试图把这些
配对的PV操作
集中到一起
这就是我们这里说到的管程
它也是一种并发程序的编程方法
那为了支持它呢
我们在底下呢
还得再加上一个条件变量
这是在管程内部呢
使用的一种同步机制
下面我们就具体来看管程的做法
管程是一种多线程
互斥访问共享资源的程序结构
它采用的是面向对象的方法
也就说把共享资源相关的PV操作
集中到一起从而简化
线程之间的同步控制
它保证的是任何一个时刻
最多只有一个线程
在执行管程代码
那从这句字面上解释呢
大家觉得管程好像
和我们的临界区是一样的
临界区也是允许一个线程
在临界区执行代码
管程也是这样
那这两者有什么区别呢
它的区别体现在下边这一条
正在管程中执行的线程
可以临时放弃管程的互斥访问
等待事件出现时恢复
这指什么意思呢
一个线程在临界区当中执行
它必须执行到它退出临界区
它才可能放弃临界区的互斥访问
而管程呢 允许我在执行的过程当中
临时放弃
那临时放弃之后呢
那其它的线程呢
就可以进到管程这个区域里头了
这种做法呢一种形象的比喻
像我们在赛车场
假定赛车场的赛道呢
为了安全我只允许
一辆跑车在上头跑
那它在跑的过程当中呢
它可能会加燃料
可能会更换零件
那在这个更换零件或者说
加燃料这个时间呢
它处于暂停的状态
这时候允许其他的赛车进入赛道
这是管程的一个基本比喻办法
假定它能干活了
那我们这时候
会需要做什么呢
管程的使用就是
我把我现在要同步的这些模块当中
收集共享的这些数据
然后编写对这些
共享数据的访问方法
有了这个之后
那在各处用的时候呢
它就不用在其它地方再做
同步互斥这些操作了
那怎么做到这一点呢
首先我们把这个区域里的代码
理解为我们的管程
如果说你把它
视为是一个临界区的话
那这时候呢
只需要在入口的地方
加一个互斥访问的锁就可以
管程也是一样
在这个地方入口队列里头呢
它只允许一个线程在管程内部执行
如果说在这个内部
没有其它共享数据的话
这时候呢就和我们的
临界区是完全一样的
任何一个线程
在进来的时候都在入口排队
允许只有一个线程进来
到它退出的时候
另外的线程可以进去
好 管程在这个临界区
基础上又加了一条
就是零个或者是多个条件变量
如果是零个的话
那就等同于一个临界区
如果是其中有一个以上的条件变量
那么这时候呢
就是我们这里管程所特有的了
它用来管理共享数据的并发访问
需要这些共享资源的时候
那么在这儿呢对应着
有相应的条件变量允许有了
那你才可以
使用中间这些互斥的操作
这是管程的基本思路
那这中间就用到一个条件变量
条件变量是什么呢
条件变量是管程内部的等待机制
进入管程的线程
因为资源占用而进入等待状态
那这时候呢它就等在条件变量上
每一个条件变量呢
表示一种等待原因
它对应着一个等待队列
有了这个之后 我们在上面
再附着两个相应的操作
一个是等待
那等待将自己堵塞在
等待队列当中 同时唤醒
另外一个者 或者说
放弃管程的互斥访问
后面这两种情况就是
允许另外一个线程进入管程
它自己呢等在这个
管程内部的等待队列上
这是我们这里的条件变量
还有一个操作呢是释放操作
这个释放操作呢
它将等待队列当中一个线程唤醒
如果说没有
这就相当于是一个空操作
好 有了关于条件变量
这种约定之后
那下面是它的实现
从这儿呢我们看到
它和我们前面讲的
信号量的实现呢很接近
它在这儿条件变量
是有一个整型变量和一个等待队列
只是说跟我们的信号量不同呢
条件变量的初值是0
而在信号量里头呢
它的初值是和你的资源数是一致的
然后对应着它有两个操作
Wait和Signal 等待和这里的释放
那等待在里面做什么呢 计数加1
好 那就相当于我正数表示其中
有线程处于等待状态
然后把它自己放到等待队列当中
释放管程的互斥访问权
然后执行调度
这实际上就说有了调度之后呢
我们就可以切换到另外的进程
或者是线程来执行
等到它回来的时候
它再去请求管程的访问权限
而释放呢
它都是放在这一个if语句里头
也就相当于如果说
这个条件不成立的话
这个操作相当于是空操作
好 条件成立也就说有另外的线程
等在这个条件变量上
它会做一些什么呢
好 把这个线程从等待队列里移出来
放到就绪队列里头 让它可以执行
然后把它的计算减1
也就说这时候等的这个
线程的数目呢少了一个
好 基于这个实现
那我们就可以来看
如何用管程来实现
生产者 消费者问题
这是呢 刚开始的初始化定义
两个条件变量一个入口等待队列
入口的锁
这和我们前面用信号量解决
生产者 消费者问题的设置呢很类似
然后在这管程内部的值呢
它当时是0
这个数呢用来表示
你写的缓冲区里的数据的数目
然后我们看这时候生产者
和消费者分别对应着一个函数
这是我们管程的封装作用
把它封装到这两个函数里头
其它地方要使用的时候呢
只需要调生成数据和读取数据
这两个函数就可以了
那这里头本质性的操作呢
是往缓冲区里写一个数据
并且把这个计数加1
消费者呢是从缓冲区里
读一个数据并且把计数减1
好 那我们在做这件事情之前
各自需要一些什么准备呢
首先我把它放到一个管程里头
这是由管程进入的申请和释放
那这是在管程内部
你可以理解为这两个函数
是构成我们管程的内部的代码
我申请到了管程的
互斥访问权之后
那我在这里头呢
我就可以往里写数据了吗
不是 我得先看你是不是有空地
那在这儿 就是去看是否有空地
如果说没有
就相当于我这里头
N个缓冲区里都有数据了
那么这时候呢
我就等待一个条件变量上
notfull 等在这个条件变量上
有空地之后呢
我再回来往里写数据
这边呢跟我们前面的
信号量做法类似的
在消费者这头呢
是读出一个数据之后
它就释放一个条件变量
说如果你在那里头有生产者
等在需要空闲缓冲区的情况
那么这时候它就把它唤醒了
那这时候有一个问题
我们用信号量解决的时候
我必须先检查缓冲区的状态
然后再申请互斥操作
那现在在管程里头你把它倒过来
这样就没问题了吗
实际上这里的原因在于
因为管程我在内部检查的时候
如果不成功我还可以放弃
管程的互斥访问权限
而在信号量那个地方实现里头
我进到那个临界区里头了
好 我已经占用了
对缓冲区的互斥访问
那这时候别人就再进不来了
因为我没有办法放弃
所以这是这两者之间的区别
也正是由于这个区别
我可以把这个检查空满的判断呢
放在管程的内部
跟这相对应
消费者呢读数据之前
需要检查是否缓冲区里头有数据
如果说没有
就这个计数为0
那么这时候呢
它有放弃管程的使用权
等在这个非空的这个条件变量上
好 一旦里头有数据之后
那有数据呢是生产者
写完数据之后它做释放操作
写完数据之后那这边释放
好 它出来再检查成功之后
它就可以往外读数据了
好 那这是呢我们用管程来实现
生产者消费者问题的解法
我们比较基于信号量
和管程的实现方案
我们可以看到在管程里头呢
它可以把PV操作都
集中到一个模块里头
从而呢简化和降低
同步机制的实现难度
在这里头我们
还有一个问题需要来讨论
管程里头条件变量
到底是内部的线程优先执行
还是正占用管程处于执行状态的
这个线程有更优先权限来执行
这两种不同的处理办法呢
对应到我们这里的两种不同管程
一种叫Hanson一种叫Hoare
这两种做法的区别是这样的
在Hanson管程里头呢
一个线程T1等待条件变量
那么它进入等待状态
这时候允许进程T2开始执行
在T2执行过程当中呢
等待的条件成立了
好 那这地方呢它在管程里头呢
这T2给了一个释放
允许x条件变量所对应的线程
可以开始运行
那在Hanson的做法里头呢
它释放完了之后
当前这个T2还会继续执行
一直到它放弃
管程的互斥访问权限
然后T1才恢复执行
这种做法呢
是当前正在执行的这个线程更优先
而Hoare的做法呢是倒过来
第一个线程执行到
等待条件变量的时候
进入等待状态
第二开始执行
这跟前面都是一样的
它们区别在于T2认为
T1等待的事件已经出现了
好 那这时候它唤醒T1
唤醒完了之后这时候
它立即放弃管程的互斥访问权
好 这时候T1马上开始执行
等它执行结束之后
T2再继续执行
如果说我们从通常的
优先的角度来讲
T1更优先是合理的
这个呢在我们现在的
基本原理介绍里头呢
Hoare做法
是更容易证明它的确定性
而第一种呢
它连续执行 它的效率会更高
在这儿呢就少了一次切换
所以这两种做法呢
目前在真实系统里头呢
一般用的是Hanson的做法
而在教科书里头呢
用的是Hoare的做法
基于这两种不同的做法
我们再来看
它在生产者消费者问题当中的体现
这是我们刚才生产者消费者
问题当中基于管程的实现代码
好 两种不同做法它们的区别在哪呢
在这个地方 一个是while一个是if
这两种区别怎么体现出来呢
在这个地方
条件变量等待的时候
这种释放呢它仅仅是一个提示
你回来之后你要重新去检查
这是while的含义
再次检查呢
实际上相当于我要再重新去排队
因为在前面那个释放操作里呢
释放完之后它还会再继续执行
有可能在这个执行过程当中呢
这个地方的状态是会有变化的
好 也正是由于这个原因
在Hanson的做法里头呢
当前正在执行那个线程优先级更高
好 那这个地方呢你需要重新来检查
所以这个地方是while
而在Hoare的做法里头呢
条件变量的释放操作
表示条件变量的立即释放
和放弃管程使用权
那这时候呢
它出来之后不再需要进行检查
而直接开始后续的执行
那这就是等待条件变量
那个线程它的优先级更高
这两种做法呢
明显是第一个效率更高
第二个呢效率会低一些
因为它多了一次切换
当然第二个行为的确定性
会更好一些
所以一个用在基本原理里头
一个是在实际系统里头
-0.1 Piazza讨论区
--html
-0.2 在线实验平台
--实验平台使用帮助
--平台使用帮助
-0.2在线实验平台
--Raw HTML
-1.1 课程概述
--视频
-第一讲 操作系统概述--练习
-1.2 教学安排
--视频
-1.3 什么是操作系统
--Video
-1.4 为什么学习操作系统,如何学习操作系统
--Video
-1.5 操作系统实例
--视频
-1.6 操作系统的演变
--视频
-1.7 操作系统结构
--视频
-2.1 前言和国内外现状
-2.2 OS实验目标
-2.3 8个OS实验概述
-2.4 实验环境搭建
-2.5 x86-32硬件介绍
-2.6 ucore部分编程技巧
-2.7 演示实验操作过程
--Q6
--Q7
--Q10
-3.1 BIOS
--3.1 BIOS
-3.2 系统启动流程
-3.3 中断、异常和系统调用比较
-第三讲 启动、中断、异常和系统调用--3.3 中断、异常和系统调用比较
-3.4 系统调用
--3.4 系统调用
-第三讲 启动、中断、异常和系统调用--3.4 系统调用
-3.5 系统调用示例
-3.6 ucore+系统调用代码
-4.1 启动顺序
--4.1 启动顺序
-4.2 C函数调用的实现
-4.3 GCC内联汇编
-4.4 x86中断处理过程
-4.5 练习一
--4.5 练习一
-4.6 练习二
--4.6 练习二
-4.7 练习三
--4.7 练习三
-4.8 练习四 练习五
-4.9 练习六
--4.9 练习六
-5.1 计算机体系结构和内存层次
-5.2 地址空间和地址生成
-5.3 连续内存分配
-5.4 碎片整理
--5.4 碎片整理
-5.5 伙伴系统
--5.5 伙伴系统
-第五讲 物理内存管理: 连续内存分配--5.6 练习
-6.1 非连续内存分配的需求背景
-6.2 段式存储管理
-- 6.2 段式存储管理
-6.3 页式存储管理
-6.4 页表概述
--6.4 页表概述
-6.5 快表和多级页表
-6.6 反置页表
--6.6 反置页表
-6.7 段页式存储管理
-第六讲 物理内存管理: 非连续内存分配--6.8 练习
-7.1 了解x86保护模式中的特权级
-第七讲 实验二 物理内存管理--7.1 了解x86保护模式中的特权级
-7.2 了解特权级切换过程
-第七讲 实验二 物理内存管理--7.2 了解特权级切换过程
-7.3 了解段/页表
-第七讲 实验二 物理内存管理--7.3 了解段/页表
-7.4 了解UCORE建立段/页表
-第七讲 实验二 物理内存管理--7.4 了解UCORE建立段/页表
-7.5 演示lab2实验环节
-8.1 虚拟存储的需求背景
-8.2 覆盖和交换
-8.3 局部性原理
-8.4 虚拟存储概念
-8.5 虚拟页式存储
-8.6 缺页异常
--8.6 缺页异常
-9.1 页面置换算法的概念
-9.2 最优算法、先进先出算法和最近最久未使用算法
-第九讲 页面置换算法--9.2 最优算法、先进先出算法和最近最久未使用算法
-9.3 时钟置换算法和最不常用算法
-第九讲 页面置换算法--9.3 时钟置换算法和最不常用算法
-9.4 Belady现象和局部置换算法比较
-第九讲 页面置换算法--9.4 Belady现象和局部置换算法比较
-9.5 工作集置换算法
-第九讲 页面置换算法--9.5 工作集置换算法
-9.6 缺页率置换算法
-第九讲 页面置换算法--9.6 缺页率置换算法
-9.7 抖动和负载控制
-10.1 实验目标:虚存管理
-第十讲 实验三 虚拟内存管理--10.1 实验目标:虚存管理
-10.2 回顾历史和了解当下
-第十讲 实验三 虚拟内存管理--10.2 回顾历史和了解当下
-10.3 处理流程、关键数据结构和功能
-第十讲 实验三 虚拟内存管理--10.3 处理流程、关键数据结构和功能
-10.4 页访问异常
-第十讲 实验三 虚拟内存管理--10.4 页访问异常
-10.5 页换入换出机制
-第十讲 实验三 虚拟内存管理--10.5 页换入换出机制
-11.1 进程的概念
-第十一讲 进程和线程--11.1 进程的概念
-11.2 进程控制块
-第十一讲 进程和线程--11.2 进程控制块
-11.3 进程状态
-第十一讲 进程和线程--11.3 进程状态
-11.4 三状态进程模型
-11.5 挂起进程模型
-第十一讲 进程和线程--11.5 挂起进程模型
-11.6 线程的概念
-第十一讲 进程和线程--11.6 线程的概念
-11.7 用户线程
-第十一讲 进程和线程--11.7 用户线程
-11.8 内核线程
-第十一讲 进程和线程--11.8 内核线程
-12.1 进程切换
-第十二讲 进程控制--12.1 进程切换
-12.2 进程创建
-第十二讲 进程控制--12.2 进程创建
-12.3 进程加载
-第十二讲 进程控制--12.3 进程加载
-12.4 进程等待与退出
-第十二讲 进程控制--12.4 进程等待与退出
-13.1 总体介绍
-13.2 关键数据结构
-13.3 执行流程
-13.4 实际操作
-14.1 总体介绍
-14.2 进程的内存布局
-14.3 执行ELF格式的二进制代码-do_execve的实现
--14.3 执行ELF格式的二进制代码-do_execve的实现
-14.4 执行ELF格式的二进制代码-load_icode的实现
--14.4 执行ELF格式的二进制代码-load_icode的实现
-14.5 进程复制
-14.6 内存管理的copy-on-write机制
-15.1 处理机调度概念
-第十五讲 处理机调度--15.1 处理机调度概念
-15.2 调度准则
-15.3 先来先服务、短进程优先和最高响应比优先调度算法
--15.3 先来先服务、短进程优先和最高响应比优先调度算法
-第十五讲 处理机调度--15.3 先来先服务、短进程优先和最高响应比优先调度算法
-15.4 时间片轮转、多级反馈队列、公平共享调度算法和ucore调度框架
--15.4 时间片轮转、多级反馈队列、公平共享调度算法和ucore调度框架
-第十五讲 处理机调度--15.4 时间片轮转、多级反馈队列、公平共享调度算法和uc
-15.5 实时调度和多处理器调度
-第十五讲 处理机调度--15.5 实时调度和多处理器调度
-15.6 优先级反置
-第十五讲 处理机调度--15.6 优先级反置
-16.1 总体介绍和调度过程
-16.2 调度算法支撑框架
-16.3 时间片轮转调度算法
-16.4 Stride调度算法
-17.1 背景
--17.1 背景
-17.2 现实生活中的同步问题
-第十七讲 同步互斥--17.2 现实生活中的同步问题
-17.3 临界区和禁用硬件中断同步方法
-第十七讲 同步互斥--17.3 临界区和禁用硬件中断同步方法
-17.4 基于软件的同步方法
-第十七讲 同步互斥--17.4 基于软件的同步方法
-17.5 高级抽象的同步方法
-第十七讲 同步互斥--17.5 高级抽象的同步方法
-18.1 信号量
--18.1 信号量
-第十八讲 信号量与管程--18.1 信号量
-18.2 信号量使用
-第十八讲 信号量与管程--18.2 信号量使用
-18.3 管程
--18.3 管程
-第十八讲 信号量与管程--18.3 管程
-18.4 哲学家就餐问题
-18.5 读者-写者问题
-19.1 总体介绍
-19.2 底层支撑
-第十九讲 实验七 同步互斥--19.2 底层支撑
-19.3 信号量设计实现
-第十九讲 实验七 同步互斥--19.3 信号量设计实现
-19.4 管程和条件变量设计实现
-第十九讲 实验七 同步互斥--19.4 管程和条件变量设计实现
-19.5 哲学家就餐问题
-20.1 死锁概念
-第二十讲 死锁和进程通信--20.1 死锁概念
-20.2 死锁处理方法
-第二十讲 死锁和进程通信--20.2 死锁处理方法
-20.3 银行家算法
-第二十讲 死锁和进程通信--20.3 银行家算法
-20.4 死锁检测
-第二十讲 死锁和进程通信--20.4 死锁检测
-20.5 进程通信概念
-第二十讲 死锁和进程通信--20.5 进程通信概念
-20.6 信号和管道
-第二十讲 死锁和进程通信--20.6 信号和管道
-20.7 消息队列和共享内存
-第二十讲 死锁和进程通信--20.7 消息队列和共享内存
-21.1 文件系统和文件
-第二十一讲 文件系统--21.1 文件系统和文件
-21.2 文件描述符
-第二十一讲 文件系统--21.2 文件描述符
-21.3 目录、文件别名和文件系统种类
-第二十一讲 文件系统--21.3 目录、文件别名和文件系统种类
-21.4 虚拟文件系统
-第二十一讲 文件系统--21.4 虚拟文件系统
-21.5 文件缓存和打开文件
-第二十一讲 文件系统--21.5 文件缓存和打开文件
-21.6 文件分配
-第二十一讲 文件系统--21.6 文件分配
-21.7 空闲空间管理和冗余磁盘阵列RAID
-第二十一讲 文件系统--21.7 空闲空间管理和冗余磁盘阵列RAID
-22.1 总体介绍
-第二十二讲 实验八 文件系统--22.1 总体介绍
-22.2 ucore 文件系统架构
-第二十二讲 实验八 文件系统--22.2 ucore 文件系统架构
-22.3 Simple File System分析
-第二十二讲 实验八 文件系统--22.3 Simple File System分析
-22.4 Virtual File System分析
-第二十二讲 实验八 文件系统--22.4 Virtual File System分
-22.5 I/O设备接口分析
-第二十二讲 实验八 文件系统--22.5 I/O设备接口分析
-22.6 执行流程分析
-23.1 I/O特点
--视频
-第二十三讲 I/O子系统--23.1 I/O特点
-23.2 I/O结构
--816C80A0F5E3B8809C33DC5901307461
-第二十三讲 I/O子系统--23.2 I/O结构
-23.3 I/O数据传输
--C58221E14388B9DB9C33DC5901307461
-第二十三讲 I/O子系统--23.3 I/O数据传输
-23.4 磁盘调度
--567A3F1FCBFB3F4C9C33DC5901307461
-第二十三讲 I/O子系统--23.4 磁盘调度
-23.5 磁盘缓存
--C327536B80D25CE79C33DC5901307461
-第二十三讲 I/O子系统--23.5 磁盘缓存
-html
--html