当前课程知识点:操作系统 > 第十八讲 信号量与管程 > 18.2 信号量使用 > 18.2 信号量使用
下面我们来介绍
如何利用信号量
来解决同步互斥问题
这就是我们这里说的
信号量的使用
那首先呢我们把信号量
可以分成两类
一类叫二进制信号量
它的初值呢是0或者是1
而另一类呢叫资源信号量
它的资源数目呢
可以是任意的非负整数
这两类信号量呢它们俩是等价的
也就说我用其中的一个
可以构造出另一个
可以由第一个构造出第二个
也可以由第二个构造出第一个
在使用信号量来解决
我们的同步互斥问题的时候
我们可以把它分成这样两种情况
一种呢是互斥访问
也就说在用它来控制临界区的互斥
另一个呢是用它来做同步
在两个线程之间实现事件的等待
首先呢我们来看
它具体是如何在做
我们在这里的例子呢
是用信号量实现临界区的互斥访问
这时候做法呢
是用一个信号量
来对应一个临界区
那这时候呢 由于我们说
任何一个临界区在任期一个时刻
只允许一个线程执行临界区的代码
那这时候它的初值是1
好 那我们先是完成它的初始化
在这里呢初值取1
然后我们来看
它如何来控制临界区的访问
那这是它的控制代码
在前面呢有一个P操作
在后面呢有一个V操作
P操作完成申请 V操作完成释放
它怎么来实现这个临界区的互斥呢
首先这个信号量初始值为1
所以第一个线程进来的时候
它由它 把它1变成0
所以它能进去 好
如果说这时候有第二个线程
也想进入临界区
那么刚才第一个还在里头运行
第二个来说它0变成-1
好 那这时候呢
它就在P操作这个地方进入等待状态
好 那这时候
在临界区的第一个线程
它执行结束 进行V操作
那么V操作呢就会导致
它把刚才的这个信号量的计数
由-1改成0
那这时候呢它知道有
第二个线程在里等着呢
好 那么这个时候呢
它唤醒第二个线程
它继续往后走
那这个时候呢 第二个线程
就会在第一个结束的时候进入临界区
那么利用这种做法呢
我们就实现了临界区的互斥访问
那用它来做的时候呢
有几点需要注意的
第一个呢 这里的PV操作
必须成对使用
也就说你有一个申请
有一个释放
如果说 不申请你直接释放
会出现什么情况
就会有多个线程
进入到临界区里头
如果说你只申请不释放
那么这时候呢 就会出现
缓冲区没有任何一个线程
但是谁也进不去的情况
用P操作保证互斥
用V操作保证资源的释放
然后这两个的顺序呢
颠倒或者重复
都会导致可能的麻烦
第二个呢 我们用信号量
来实现条件同步
那这时候呢也是用一个信号量
来描述一个条件同步
那它的初值是0
也就说在起头的时候
那个事件还没出现
出现之后 那这对于我们来说
是一个资源 它就变成1了
好 等待资源你申请
那这时候事件出现之后
那等待的就可以继续往下走
具体怎么做呢
初值它设成为0
我有两个线程A和B
分别有mn和xy各自的两个模块
那我们需要做的事情呢
是在B执行到x之后
这个A才能执行n模块
比如说我在这里这边要准备数据
这边是使用数据
如果说你的数据没准备好
那你要想使用数据
比如说n模块是
对数据进行处理的话
那你就没有可以处理的数据
它们俩之间呢
必须等到B执行完x
A才能执行n
好 那这时候我们怎么办呢
在这里呢 一个P操作一个V操作
在B里头 释放信号量的
好 那么这时候它就会由0变成1
好 如果说进程B
先执行完x 到达这个点
那么这个时候呢
A执行完m的时候
这个地方信号量的值就是1了
好 它就直接能往下执行了
假定不是这种情况
是A先执行完m
那么这时候呢它来申请
进行P操作
这时候呢信号量的初值是0
那么这时候它变成-1
这时候它堵塞 进入等待状态
好 然后这个进程B呢
执行完x到这儿它释放
释放的时候我们刚刚还记得
信号量的V操作的实现里头
它是加一 如果说加完之后
它是小于等于0的
那这时候知道
有另外一个线程A在等待
好 那这时候唤醒A 它继续往后走
这时候线程A呢
也可以继续往下走了
好 所以从这个地方呢
我们就实现了一个条件等待
线程A要等待线程B执行完x模块
它A才能执行n模块
这是我们用信号量
来实现同步的两个基本的情况
和我们这里的基本做法
好 那我们下面呢
来看一个更实际的例子
就是这里我们所说的
生产者 消费者问题
这个问题的基本要求呢是这样的
有一组生产者 一个或者是多个
它们负责产生数据
并且把这数据呢放在缓冲区里头
然后另外呢还有一个消费者
这个消费者呢
负责从缓冲区里读数据
进行后续的处理
比如说我们在这里呢
消费者就是对应我们打印进程
前面呢就是对应着你的应用程序
若干个应用程序
会产生打印作业
放在缓冲区里头
打印服务器呢
负责从缓冲区里
读出你的打印数据
然后进行打印
那这个缓冲区呢有一个限制
就是在任何时刻
只能有一个生产者或者是消费者
可以访问这个缓冲区
不可以两个同时来做
那它们在用的过程当中
可能会有这种情况
生产者往缓冲区里写
那我的缓冲区得有空地才能写
如果写满了 那它做不下去了
消费者从里读数据
里头得有数据它才能读
如果没有数据它得等着
好 我们就来看
如何用信号量实现
生产者 消费者之间的协调关系
我们如何用信号量来解决它呢
我们首先对这个问题呢做一些分析
从我们刚才的描述里呢
大家可以知道
实际上这个缓冲区是一个临界区
任何一个时刻
只允许一个线程对缓冲区进行操作
这可以描述成临界区
除此之外呢还有另外两条限制
第一条呢是说
消费者要从缓冲区里读数据
如果说缓冲区里没有数据 是空的
我消费者是没办法进行下去的
它必须等待生产者
往缓冲区里放了数据之后
它才能够继续往后进行操作
另一个呢是生产者
生产者必须等待缓冲区里头有空地
你才能够往里放
如果缓冲区满了 那生产者必须等待
我们把生产者消费者问题的描述呢
转换成了我们用临界区
和条件等待的方式
给它描述出来
那这三条呢 对应过来就是
我们这里的互斥访问 缓冲区
然后生产者和消费者
分别有一个条件等待
是等待对方的
那这时候呢 我们把它描述出来
就变成这样几个信号量所做的约束
第一个呢是二进制信号量
它描述这里的互斥关系
第二个就是fullBeffers
它对应着 有数据我才能够往外读
所以消费者必须等待缓冲区里有数据
然后再有一个呢 是生产者
生产者必须等待缓冲区有空地
你才能往里写
好 这两者对应这两个信号量
它加起来之后呢
应该是你缓冲区的总的大小
好 有了这些分析之后呢
我们基本上就把我们这里的
生产者消费者这一个同步的问题
转换成我们刚才说的
互斥访问和条件等待
那接下来呢
我们把它转换成实际的代码
首先呢 是对它的初始化
二进制信号量
它的初值是一个临界区 初值为1
好 然后刚开始的时候
缓冲区里没有任何数据
所以缓冲区满
这个地方它的初值是0
好 然后说有多少个空缓冲区呢
每个空缓冲区对应着一个资源
生产者 消费者它是如何在做
这是生产者 这是消费者
它们对应的代码
那在这里呢 往缓冲区里放数据
从缓冲区里读数据
是它们要进行的主要操作
那首先第一个
我们要实现这个是互斥访问临界区
所以在这儿呢
有一个P操作 有一个V操作
是申请这个二进制信号量mutex
好 在这边呢也是一样
好 实现他们俩之间
对缓冲区的互斥访问
它仅有互斥是不够的
如果说缓冲区里头没有空地
你即使申请到了缓冲区的互斥操作
你也没办法操作
因为里头没空地了
而这时候你又把缓冲区占掉了
消费者已经没有办法帮你腾出空地来
所以在这儿之前呢
我需要来检查是否有空缓冲区
有 我可以再进一步检查
是否有另外一个生产者或者消费者
在访问缓冲区
好 如果没有
那它就可以往里放数据了
那这一条有没有缓冲区
它在什么时候会去释放呢
那消费者在从里头读出数据之后
它在这里呢会释放一个空缓冲区资源
也就相当于我给你腾出一个空地来
而另一头呢
消费者要想进入的时候呢
它得看里头是否有数据
好 在这个地方呢 负
那实际上就是我看里头是否有数据
那只有生产者往里写了数据之后
我这个地方呢才能有数据资源
好 那你才可以从里头读数据
生产者在往里写了数据之后
它就释放一个fullBuffer
这样一个资源
释放这个资源之后
实际相当于我这个里头就多了数据
好 这时候这个消费者呢
他们俩是配合
这时候有一个问题
我在这个地方这个顺序可以调整吗
在这里头 如果说
你把这两个顺序调整了
它就会出现死锁
原因是在于我检查空和满
你需要先检查
然后我再去申请互斥访问
如果说你申请了互斥访问
那这个时候你已经占用了临界资源
那别人不能读写了
而这时候你又发现里头是满
或者发现里头是空
那这时候你进行不下去了
但这时候呢别人也没有办法
来申请到这个临界区
那这时候它就会形成死锁
这是用信号量机制
来解决生产者消费者问题
从这个应用的例子当中我们可以看到
实际上用信号量来解决同步问题
它是需要你有技巧的
程序员在写的时候
你必须很好的了解你的问题
和很好的运用信号量机制
并且这时候呢
它是容易出错的
我可能在某个地方
忘了一个P操作
或者忘了一个V操作
或者说我把它的顺序搞倒了
这些呢都是容易出问题的
所以它没有办法避免死锁的出现
你必须在你写程序的时候
来解决这个问题
-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