各位同学 大家好 在这个小节中 我们要学习怎么用信号量机制来实现进程的同步互制关系
那么 我们之前学习了互斥的几种软件实现方式和硬件实践方式
但是这些实现方式都有一个共同的缺点 就是没有办法实现让权等待这个原则
而信号量机制当中设置了进程的阻塞和唤醒就刚好可以解决让权等待这个问题
所以信号量机制是一种更先进的解决方式 那我们上个小节介绍了信号量是什么 信号量机制的pv操作 分别做了一些什么事情
建议是不要先一头钻进大马里 而是要注意理解信号量背后的含义
其实一个信号量 它无非就是对应了某一种资源 而信号量的值
表示的是这种资源的剩余数量 如果它的值小于零的话 那么就说明此时至少有一个进程正在等待
这种资源 而 如果一个进程执行了对某一个信号量的p操作 那么他想表达的其实是
申请一个这种资源 如果资源不够的话 这个进程就会执行block原语主动的阻塞
而如果一个进程他对某一个信号量执行V操作的话 那么就说明这个进程想要释放一个这种资源 想要产生一个这个资源
如果此时有进程正在等待这个资源 那么他就会用we cup原语唤醒一个正在等待的阻塞进程
那我们来看一下如何利用这种机制实现进程的互斥 那经过之前的学习我们知道
系统当中的某一些资源是必须互斥访问的 而访问这种系统资源的那段代码叫做临界区
所以 既然这个资源需要互斥访问 那么就说明同一时刻只能有一个进程进入临界区代码
所以 要解决进程互斥的问题 我们首先要做的是要划定临界区
也就是说 哪一段代码是用于访问临界资源的 另外 为了实现对临界区的互斥访问 我们需要设置一个互斥信号量
叫mutex mutex就是英文的互斥的意思 把这个信号量的初始值设为一
就像这个样子 然后当一个进程要进入临界区之前 需要对new tax执行p操作
在出了临界区之后 需要对new tax执行v操作 所有的进程都是这样 这样就可以实现各个进程对临界区这段代码的
互斥访问了 那我们在开篇提到过信号量 它其实就是用于表示某一种资源
那我们可以这么理解这个互斥信号量 mutex 我们可以认为他所表示的资源是
进入临界区的名额 它的初始值为一那么就说明刚开始可以进入这个临界区的名额只有一个
那当某一个进程对new tax执行p操作的时候 其实在背后的逻辑就是说
我想申请一个进入临界区的名额 那如果名额这种资源此时还有剩余的话
那么这个进程就可以顺利进入临界区 而如果此时另一个进程也尝试
执行p操作 也就说他也尝试申请一个名额 那么我们知道这个名额总共只有一个 所以此时这个进程对于名额这种资源的申请就得不到满足
他必须阻塞在这个地方等待 而直到这个进程他使用完临界区之后
他又对mutex执行v操作 也就说他会归还这个名额 那在这个时候就可以把p二进程给唤醒 让他进入临界区
所以可以看到 我们用这样的方式就实现了各个进程对临界区的互斥访问
那在上个小节中 我们对信号量是这么定义的 三部分 这个其实就是信号量的
英文单词那这个地方需要提醒大家的是 如果题目没有特别说明的话
我们对一个信号量的定义只需要像这个题目这样 semaphore mutex等于多少
用用这种方式来定义就可以了 我们并不需要写出这个信号量的数据结构 当然 大家也要能够自己写出信号量定义的这个数据结构
那这个地方 我们虽然使用了这样一个简单的方式来定义 但是只要我们用semaphore这个关键字来开头的话 那么就意味着这个信号量 它并不是整形信号量
它是一个记录型的信号量 也就是说 这个信号量是带有排队阻塞的这个功能的
并不会盲等 那这是大家在做题的时候需要注意的第一个点 第二个点 对于不同的临界资源 我们需要设置不同的互斥信号量
比如说 我们的系统中有p一和p二这两个进程 他们需要啊访问打印机这种临结资源
而p三和p四他们需要访问摄像头这个临界资源 那在这种情况下
我们要给访问打印机的零接区设置一个信号量mutex一要给访问摄像头的这个零接区设置另一个信号量mutex二
另外 必须注意的是 pv操作必须承兑的出现 如果缺少这个p操作
那么就没办法保证各个进程互斥的访问 临接资源的这个事情 如果缺少V操作的话 那么就有可能会导致某一些进程阻塞了 之后永远得不到唤醒
那这是大家在做题的时候需要注意的三个点 接下来我们再来看第二个问题
怎么用信号量机制来实现进程的同步 那进程的同步这个概念我们已经有一段时间没提了
这再来 简单的复习一下 所谓的同步 就是说要让各个病发执行的进程按照要求的顺序有序的推进
比如说有p一p二这两个进程 当他们在系统当中并发的执行的时候
由于系统的环境很复杂 所以操作系统在调度的时候 有可能是p一线上处理机运行 有可能是p二线上处理机运行
比如说p二线上处理机运行了代码四和代码五而此时他时间片用完了 那又切换回p一
然后p一运行了代码一代码二接下来又切换为p二运行了代码六等等等等
总之 由于这两个进程在系统中是并发的运行的 因此他们之间的这些代码执行先后顺序是我们所不可预知的
而有的时候 我们又必须让这些代码的执行顺序按照我们想要的那种方式进行
比如说 当这两个进程并发执行的时候 p二的代码四必须基于p一的代码一代码二的
运行结果才可以执行 那么在这种情况下 我们就必须保证代码四是在代码一和代码二之后才执行的
所以这就是所谓进程同步的问题 我们要解决他们之间并发运行存在的异步性
让他们按照我们想要的顺序相互配合着有序的推进 那我们怎么用信号量机制来实现进程同步呢
首先 我们要做的就是要分析在什么地方需要实现所谓的同步关系
也就是说要在什么地方需要保证所谓疫前以后的两个操作 某一个操作一定要在前
而另一个操作一定要在后 这是所谓疫前以后的意思 第二步我们要设置一个同步信号量s
他的初始值为零那以刚才p一p二为例 如果代码四必须在代码二之后才能执行的话
那么 p二在代码四之前需要对s这个信号量执行一个p操作 而p一在代码二之后需要对s这个信号量执行一个v操作
我们来分析一下会发生什么情况 假如刚开始是p一被调度 他线上处理运行
那么p一运行了代码一代码二之后执行了v操作 这个v操作会导致s的值加一
这个值本来是零那在加一之后 s的值变为了一那接下来如果p二上处理机运行的话
当他对s这个信号量执行p操作的时候 就会发现s的值是一
表示有可用的资源 所以p二这个进程并不会被阻塞 它就可以往下执行代码四
那刚才我们所说的这种情况是先执行了代码一代码二然后再执行了代码四
那再来看第二种情况 假设刚开始是p 二先上处理机运行
那么 p二首先会对s这个信号量执行一个p操作 那 由于s的值刚开始是零
所以p二会在这个地方被阻塞 他暂时没办法运行代码四而直到p一上处理机运行运行了代码一代码二之后
他对s这个信号量执行 赢得了一个v操作 那根据上个小节我们讲的逻辑
这个v操作会唤醒此时正在等待s这个信号量的进程 也就是会唤醒p二这个进程
也就是说 只有p一执行了v操作之后 p二才有可能被唤醒 上处理机运行
那这就保证了代码21定是在代码四之前执行的 我们依然是用信号量代表某种资源 这样的思路来分析这个问题
这个信号量s 它表示某种资源 那具体是什么资源没必要关心
刚开始s的值是零就意味着刚开始这种资源是没有的 而p二要执行
代码四之前 他一定需要获得这个资源 所以他在执行代码四之前需要执行一个p操作
但是呢 这种资源只有p一能够释放 所以当p二申请这种资源得不到满足的时候 它就会被阻塞
而由于这种资源只有p一能够产生 所以只有p一能够在某一个特定的位置唤醒p二这个进程
所以这就实现了进程之间的同步关系 那么我自己对这种同步关系进行了一个小小的总结
如果要实现进程之间的同步 那么我们需要在前操作之后执行v操作
在后操作之前执行p操作 什么意思呢 代码二是必须在前执行的
代码四是必须在后执行的 所以代码二是所谓的必须在前的这个操作
代码四是必须在后执行的操作 因此我们需要在前操作 也就是代码二之后
执行一个v操作 在后操作 也就是代码四之前执行一个p操作
那我们再把这个表述精简一点 就是前微后批 我们设置一个信号量 初始值为零表示刚开始 这种资源是没有的
而只有执行前面那个操作的进程可以释放这种资源 所以这是前威
而执行后面那个操作的进程 在他的操作之前需要申请一个这个资源
所以这是后屁 当他申请的这个资源得不到满足的时候 这个进程就会阻塞
只能由前面那个进程把它唤醒 那这个技巧是解决进程同步问题的一个关键
我们来看一下怎么利用这个技巧来解决更复杂的进程同步问题 我们的课本上专门讲了一个叫做进程的前驱关系
那这个图很好理解 其实他想表达的就是 只有s一这个事件发生了之后 或者说只有s一这个代码执行了之后
才能执行s二和s三而只有执行了s二之后 才可以执行s四和s五
另外 只有执行了s三s四s五之后才能执行s六
那我们假设这几句代码分别是p一p二p三p41直到p六这几个进程需要执行的
那我们来看一下怎么用信号量机制解决这么复杂的进程之间从不问题
首先 我们需要分析的是 在这个前驱图当中 其实它包含了很多对的
进程同步关系每一条线其实就是代表一个疫前以后的同步问题
所以我们需要给每一对这种疫情以后的这种同步关系都设置一个同步信号量
那我们这就分别用a b c d e f g来分别表示啊 每一个这种同步关系
并且同步信号量的初值都是零也就说这种资源刚开始是没有的 这种资源只能由前面这个操作相关的进程
来产生那 由于s一必须在前 s二必须在后 所以当s一这个事件发生了之后 也就这个前操作之后
我们需要对相应的信号量执行一个V操作 这是前威 而当后面这个操作
发生之前 我们需要对相应的这个同步信号量执行一个p操作 这是后p
前面这个操作完成了就执行v 后面这个操作开始前就执行p前微后p
那其他的所有的这些同步关系也都是一葫芦画瓢 所有都是前卫后皮
那这样的话 我们就可以很轻松的用p v操作实现这么复杂的进程之间的同步关系
简单的对照图看一下s一这个操作完成了之后 它需要执行两个v操作 一个是va 一个是vb
那对应的就是代码的这个部分 s一之后执行v a和v b 而s二这个代码执行之前 它需要执行一个p a操作
所以s二之前有一个p a 而s二执行之后 他又需要对c和d分别执行v操作 所以它后面又有v c和v d
那再来看s六s六这个代码执行之前 它需要对这几个同步信号量都执行p操作
所以s六之前需要进行p e pf和pg 总之 虽然这个同步关系有很多层
但是我们只要知道前威后癖这个技巧 我们就可以把这个同步关系很清晰的 很轻松的表达出来了
那这个地方希望大家暂停来分析一下这个代码 如果各个进程以不同的顺序上处理机运行的话
到底能不能实现我们这表达的这么多的同步关系呢 比如说刚开始是p五这个进程上处理机运行
那么 他所要做的第一件事是对d这个信号量执行一个p操作 而由于d刚开始的值为零所以他会被阻塞在这个地方
因此 接下来就会发生进程调度切换为另一个进程 那假设接下来上处理机运行的是p二这个进程
那他对a这个信号量执行p操作 同样的 他也会被阻塞在这个地方 那除非p一进程上处理机运行了
当他执行了s一这个代码之后 他会执行v a和v b操作 那v a这个操作会把p二这个进程给唤醒
所以接下来p二这个进程才会执行s二这个代码 也就是说 s二肯定是在s一之后执行的 这和这个前驱图所反映的关系是一样的
那当p二执行了s二这个代码之后 他又会对d这个信号量执行一个v操作
这个v操作又会唤醒刚才被阻塞的p五这个进程 那之后p五才可以执行s五这句代码
所以对于d这个信号量的p v操作也保证了s五这句代码
一定是在s二这句代码之后才能执行的 这也和我们这所反映的这一对同步关系是一样的
那剩下的就不再展开 大家可以暂停自己 给自己出一些题 来分析一下这些同步关系到底是如何被满足的
好的 那么这个小节中 我们介绍了很重要的知识点 怎么用信号量机制实现进程的互斥同步
几乎每一年都至少有个大题是要考察这个信号量机制实现互制和同步的
所以对于这个小节的掌握是十分重要的 那么如果要用信号量机制实现进程互斥的话 我们可以设置一个初始值为一的互斥信号量
并且在临界区之前执行p操作 临界区之后执行v操作 而如果要实现进程的同步的话 那么我们需要设置一个
初始值为零的同步信号量 另外 需要在前操作之后执行v操作
需要在后操作之前执行p操作 也就是前微后p 用这样的方式就可以保证
进程之间疫情以后的这种同步关系了 那这是互斥和同步问题的一个基本套路
那最后我们所介绍的进程的前驱关系 他本质上也是一个进程同步的问题 只不过他是多级的同步
但只要我们能掌握前卫后癖的这种技巧的话 前驱问题其实也很好解决
那对于信号量机制的考察除了实现互斥和同步之外 有的时候有可能会考察
用信号量机制来实现资源分配的问题 比如说系统中有三个打印机 那么这种情况下我们就需要把
打印机对应的那个信号量初始值设置为三然后当一个进程需要申请使用这个资源的时候 就需要对这个资源所对应的信号量执行p操作
然后使用完了之后 就需要执行v操作 那这一点其实只要掌握了信号量 他在背后所表示的逻辑也并不难理解
那这个小节中 我们只是简单的介绍了pv操作的一个简单的使用技巧
并没有涉及实际的题目 那从下个小节开始 我们会介绍几个很经典的进程同步问题用来帮助大家
更好的学习如何用信号量机制解决复杂的进程同步 进程互斥的问题
好的 那么以上就是这个小节的全部内容
推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习