【操作系统】进程管理——用信号量机制解决问题,以生产者-消费者问题为例(个人笔记)

news2025/4/8 4:02:47

学习日期:2024.7.10

内容摘要:利用信号量机制解决几个经典问题模型


目录

引言

问题模型

生产者-消费者问题(经典)

多生产者-多消费者问题

吸烟者问题

读者写者问题(难点)

哲学家进餐问题(避免死锁)


引言

在《信号量机制》章节中,我们已经学习了信号量的概念和用其实现进程同步和互斥的工作原理,下面结合实际的模型来看信号量机制如何起效。

PV操作题目分析步骤

1.关系分析。找出题目中描述的各个进程,分析它们之间的同步、互斥关系。

2.整理思路。根据各进程的操作流程缺点P、V操作的大致顺序。

3.设置信号量。根据题目条件确定信号量初始值(互斥一般为1,同步一般看对应资源的初始值)

简记口诀:

互斥:紧邻操作,成对PV(紧邻互斥操作,在同一进程内完成PV)

同步:前V后P,前后后前(在不同进程内进行操作以保证顺序执行,在前一个进程完成后进行V操作,在后一个进程开始前进行P操作)

问题模型

生产者-消费者问题(经典)

系统中有一组生产者进程和消费者进程,生产者每次生产一个产品放入缓冲区,消费者每次从缓冲区中取出一个产品使用(“缓冲区”理解为二者共享的仓库,“产品”理解为某种数据)

缓冲区是一个初始为空,大小固定为n,双方共享的区域,且缓冲区是临界资源,各进程必须互斥访问。(原因:如果不是互斥访问的,可能两个生产者进程在同一片区域放入数据,导致数据覆盖丢失)

首先分析,显然:

①缓冲区没满的情况下,生产者才能生产,二者有同步关系。

②缓冲区不空的情况下,消费者才能消费,二者有同步关系。

③进程对缓冲区的访问必须互斥。

那么,设置三个变量,一个mutex表示互斥标记,一个empty表示空闲区数目,一个full表示装入区数目。每次生产者进程行动时,P(empty),V(full),消费者行动时则反之

这样就能实现通过PV操作来表示资源的获取与释放。

注意!实现互斥是在同一进程中进行一对PV操作,在“把产品放入缓冲区/从缓冲区中取出”这一对临界资源的访问行为前后,成对进行。

而同步关系是在两进程分开执行,在一个进程中执行P,另一个执行V。具体理论部分可以参考上一章。

Q:能否交换相邻P操作的顺序?

不能。假如交换了生产者的前两个P操作的顺序,若缓冲区内已经放满产品,此时empty=0,full=n。若生产者先进行P(mutex)声明自己独占了缓冲区,然后P(empty)发现缓冲区的empty区不足,就会进入阻塞状态。

之后消费者想占用缓冲区,释放empty资源时,因为生产者已经占用了mutex,没有释放,因为互斥会导致无法进行V(empty)操作

这样,生产者在等待消费者释放empty,消费者在等待生产者解除对缓冲区的占用,双方会无限等待下去,形成死锁

所以表示互斥的PV部分要成对出现,成对执行,且实现互斥的P操作一定在实现同步操作的P操作之后

Q:能否交换相邻V操作的顺序?

可以,V操作不会导致进程阻塞,但是为了方便理解记忆,还是让实现互斥的PV操作紧邻互斥行动区为好。

Q:能否不依赖互斥标识资源mutex?

通常不行,但是特殊情况下可以。这个特殊情况就是缓冲区容量为1的情况(即empty的初始值为1),此时如果一个生产者生产了产品,执行P(empty),另一个生产者也想生产产品时,因为empty的值已经是0,就会发生阻塞,从而避免了生产者同时访问的问题。而在生产者执行完互斥操作,V(full)之前,因为full的值为0,消费者进行P(full)时也会阻塞,避免了生产者访问时消费者同时访问的问题,从而不依赖mutex就能实现互斥访问。

多生产者-多消费者问题

是生产者-消费者问题的变形,区别在于增加了若干组生产者和消费者。比如说1号生产者会生产1型商品,2号生产者生产2型商品,而1号消费者只消费1型商品,2消费者只消费2型商品。

所有的生产者和消费者仍旧共享一个缓冲区,此处的“多”指的是“多类”而不是“多个”,与上一问题的根本区别是有多类生产者和消费者。

关系分析:

①首先,同生产者-消费者问题的前提一样,缓冲区没满才能生产,不空才能消费,且互斥访问。

②根据不同资源的类型,分别PV不同的变量,来为每对生产者和消费者确立同步关系。

在分析同步问题的时候,不能从单个进程行为的角度来分析,而应该从事件角度分析。

不要想着“x号生产者生产了一个x号产品,x号消费者......”,然后设置四个同步信号量。而是从事件的角度分析,把进程的先后顺序概括为事件的先后顺序,在这个例子中,就是生产产品事件先于消耗产品事件。

生产产品可以是1号生产者,也可以是2号生产者,它们只需要每次占用一个仓库空间P(empty),然后增加一个对应产品V(product_x)。同样的,消费者也只要每次消耗一个产品P(product_x),然后释放一个空间P(empty)就行了。最后再在互斥操作前后加上mutex的成对PV操作保证互斥,根据空间的大小设置empty的初始值,就可以解决问题了!

吸烟者问题

仍然是生产者-消费者问题的变形。更准确的来说,是“可生产多种产品的单生产者-多消费者问题”

假设一个系统有三个抽烟者进程和一个供应者进程,每个抽烟者进程不停的卷烟并抽烟,这需要三种材料:烟草、纸卷和胶水。在三个抽烟者中,第一个有烟草,第二个有纸卷,第三个有胶水。供应者会无限提供三种材料,每次将两个材料置于桌面,而拥有剩下那个材料的抽烟者就会拿走,完成抽烟,然后给供应者一个信号告诉他完成了,供应者就会放另外两种材料在桌上,让三个抽烟者能够轮流抽烟。

首先,桌子可以抽象为容量为1,互斥访问的缓冲区。为什么放两件东西容量为1呢?

我们将供应者供应的两件东西看作是一个组合,则他一共供给三个组合,第一个消费者需要纸卷+胶水,定义为组合1,第二个消费者需要烟草+胶水,定义为组合2,同理得组合3。

这样问题就抽象为了一个生产者能生产1号,2号,3号三种产品,分别对应三个消费者,要进行轮流消费,缓冲区容量为1。

关系分析:

①桌上有组合x 先于 x号消费者取走东西,且缓冲区互斥访问。

②消费者发出完成信号 先于 生产者将下一个组合放到桌上。

Q:轮流操作如何实现?

如右图所示,通过一个if(i==0)的选择分支结构,和结尾的i=(i+1)%3,实现了让i在0,1,2不断循环变化,从而轮流执行三个部分的生产,每次产生对应的offer。补充:如果要随机实现,可以用Random(i)

而如上图每一个消费者每次消耗一个offer的商品P(offer_x),然后再释放一个finish信号给生产者,保证先释放信号,然后生产者再生产下一个产品。

之前介绍过,因为此时缓冲区的容量为1,故不需要互斥信号量mutex也可以实现互斥访问。


读者写者问题(难点)

在系统中有读者、写者两组并发进程,共享一个文件。多个读者可以同时对文件执行读操作,但是只允许一个写者往文件中写入信息,写者对文件的访问必须互斥,也不能边写边读,每个写者在工作时都必须独占该共享文件

与消费者进程不同,读者进程在读数据时,不会“消耗”数据, 所以读者进程可以同时访问共享数据,互不影响。

不能边读边写是因为,如果允许并发执行,读者可能先读了某数据的一部分,但剩下的部分被写者覆盖了,导致读者所读的数据并不是所期望的。不能同时写的原因同生产者进程互斥原因,会相互覆盖。

关系分析:

写者进程和所有进程互斥,读者进程之间不互斥

难点就在于,如何在实现写者进程互斥的同时,让读者进程不互斥。

我们一步一步来:

semaphore rw=1


writer(){
  while(1){
    P(rw);  //写之前上锁
    写文件操作...
    V(rw);  //写完解锁
    }
}

reader(){
  while(1){
    P(rw);    //读之前上锁
    读文件操作...
    V(rw);  //读完解锁
  }
}

 先写个最简单的,但是显然,这样是每个进程都互斥访问共享区,不能实现同时读操作。

那么,要如何实现“同时读”呢?可以设法让后面的读进程跳过P(rw)这一步

所以,逻辑代码变成了这样:

semaphore rw=1;
int count = 0;  //记录当前有几个读进程在访问文件
 
writer(){
  while(1){
    P(rw);  //写之前上锁
    写文件操作...
    V(rw);  //写完解锁
  }
}

reader(){
  while(1){
if(count==0)  //由第一个读进程负责上锁
    P(rw);    //读之前上锁
  count++;    
    读文件操作...
  count--;   
if(count==0)//由最后一个读进程负责解锁
    V(rw);  //读完解锁
  }
}

我们引入了一个新的变量count用来记录当前访问文件的读进程数,当count==0时,访问文件的读进程是第一个,它负责上锁,之后访问的读进程,因为count!=0,就跳过了P(rw)语句,从而可以直接进行读文件操作。同理,当最后一个读进程读完时,count==0,最后一个进程进行V(rw)释放资源,表示没有读进程还在操作了。

但是这样还是有一个问题,因为读进程是并发执行的,如果两个读进程同时开始执行,当第一个读者进程P(rw)以后,还没有来得及count++时,第二个读进程可能已经通过了conunt的判断语句,也进行P(rw),但是此时rw已经为0了,这会导致第二个进程阻塞。

显然,出现上一问题的原因是,我们对count变量的检查和赋值无法一气呵成无法一气呵成...?我们最初用PV操作就是为了解决进程互斥中,软件实现方法无法一气呵成的问题的,(详见《进程的同步与互斥》)所以可以想到,再加一组互斥标识,保证进程能互斥的访问count。

semaphore rw = 1;
semaphore mutex = 1;//用于保证对count变量的互斥访问
int count = 0;  //记录当前有几个读进程在访问文件

 
writer(){
  while(1){
    P(rw);  //写之前上锁
    写文件操作...
    V(rw);  //写完解锁
  }
}

reader(){
  while(1){
    P(mutex);
    if(count==0)  //由第一个读进程负责上锁
        P(rw);    //读之前上锁
      count++; 
    V(mutex);   
    读文件操作...
    P(mutex);
      count--;   
    if(count==0)//由最后一个读进程负责解锁
        V(rw);  //读完解锁
    V(mutex);
  }
}

 我们引入了mutex变量,来保证进程能互斥的访问count,此时如果两个读进程同步访问count,第一个进程会P(mutex)阻止其它读进程修改count,并且在自己修改完count之后,其它进程才能进行if判断,这样就不会出现上述问题了。两组对于mutex的PV操作,能够保证进程对count的互斥访问

但是,还有一个潜在的问题(受不了了),只要有读进程还在读,rw的值就始终是0,写进程就会一直阻塞,如果一直有源源不断的读进程,写进程就会饥饿。在这种算法中,读进程是优先的。那么,该如何避免写进程饥饿呢?就是说,怎样能让写进程在想写入的时候,在有限的等待时间内就能写入呢?

semaphore rw = 1;
semaphore mutex = 1;//用于保证对count变量的互斥访问
semaphore w = 1;  //用于实现“写优先”
int count = 0;  //记录当前有几个读进程在访问文件

 
writer(){
  while(1){
    P(w);
    P(rw);  //写之前上锁
    写文件操作...
    V(rw);  //写完解锁
    V(w);
  }
}

reader(){
  while(1){
    P(w);
    P(mutex);
    if(count==0)  //由第一个读进程负责上锁
        P(rw);    //读之前上锁
      count++; 
    V(mutex);
    V(w);   
    读文件操作...
    P(mutex);
      count--;   
    if(count==0)//由最后一个读进程负责解锁
        V(rw);  //读完解锁
    V(mutex);
  }
}

 我们又引入了一个新的变量w,用于实现写优先。

假如三个进程读者A,写者,读者B并发执行。

当读者进程A通过了上半部分,开始进行读文件操作时,释放了w,占用了rw。此时写者进程可以进行P(w)操作了,但是会被阻塞在P(rw)操作上。当读者进程B开始运行时,因为写进程已经进行过P(w)操作了,读者B会在P(w)处被阻塞。

这样当读者A的读操作结束时,count--后为0,读者A会认为自己是最后一个读进程,从而释放rw,这样写者就可以执行P(rw),实现了写者优先于读者B执行。

此方法并不是真正的“写优先”,只是保证了写进程不会饥饿,相对公平的实现了先来先服务。

此处的w好比一个提供顺序功能的标记,第一个读者最开始先占用,然后在读文件之前释放,第二个读者如果在写者之后来,就会因为写者已经占用了w而不能继续。

小结:

读者-写者问题为我们解决复杂的互斥问题提供了思路,核心思想在于设置一个计数器count来记录当前正在访问共享文件的读进程数,用count的值来判断当前进入的进程是否是第一个/最后一个读进程,从而跳过部分P指令,实现读进程不互斥。

在对count变量的检查和赋值不能一气呵成时,采用mutex变量来确保count部分被互斥访问。

在写进程会饥饿时,引入了w变量来确保可以公平的完成先来先服务。

哲学家进餐问题(避免死锁)

圆桌上坐着5位哲学家,每两个哲学家之间摆着一根筷子,桌子的中间是火锅,哲学家们会思考和干饭。哲学家们在思考时不会影响别人,只有想干饭时,才会拿起左、右各一根筷子(一根一根拿),如果筷子已经在他人手上则需要等待。因为火锅很烫,哲学家必须需要一双筷子才能进餐,进餐完毕后,哲学家放下筷子继续思考。

难点:这个问题中只有互斥关系,没有同步关系,但是在这个模型中,每个哲学家进程都需要同时持有两个临界资源才能开始吃饭。我们可以想到一个很尴尬的情况,那就是每个哲学家都拿了一根筷子,大家都没有办法吃饭,但是都在等待别人吃饭然后让出筷子,这就是死锁这是临界资源分配不当导致的,我们要设法避免这种情况发生。

关系分析:

①系统中有5个哲学家进程,5位哲学家与左右邻居对其之间的筷子的访问是互斥关系。

②筷子是本模型中的临界资源,哲学家想要吃饭需要获取其左右两根筷子。

我们给哲学家和筷子编号为0~4。显然,编号为i的哲学家需要编号i和i+1的两根筷子来吃饭,考虑到边界,严谨的表述是i和(i+1)%5

我们很容易想到一个简单的写法:

这种写法能实现对筷子的互斥访问,但是不能避免前面提到的死锁问题。那么该怎么解决呢?

①可以要求奇数号哲学家先拿左手的筷子,偶数号哲学家先拿右手的筷子。如图所示,以0、1为例,这样两位就会在一开始争抢1号筷子,谁先拿到,另一个就在不占用任何筷子的情况下直接进入阻塞态,这样就避免了五个人每人占用一根筷子进入阻塞态的死锁。

以代码实现的话,在每个哲学家拿筷子之前判断号码的奇偶,然后确定P操作的顺序即可。

②保证至少有一个哲学家可以一气呵成的拿到完整的一双筷子

使用mutex互斥变量,保证至少有一个哲学家可以拿到完整的一双筷子。

在拿筷子的部分前后加入了mutex的PV操作。当0号哲学家拿筷子时,先P(mutex),之后依次拿起左右的筷子,即使在这过程中发生了进程调度,别的进程也会因为mutex值已经为0被阻塞,不能拿起筷子,直到0号哲学家把筷子拿完,释放mutex以后,别的哲学家才能拿起筷子。这样能够保证,至少第一个开始拿筷子的进程会一气呵成的拿到一双筷子,避免死锁。

但是,这个方法也有一个问题。如图所示,当0号哲学家拿完一双筷子开始进餐后,4号哲学家开始拿筷子,因为此时mutex已经释放,4号哲学家顺利执行了P(mutex),且拿起了4号筷子,当想拿起0号筷子时,发现被占用,进入阻塞态。

     此时,对于2号哲学家来说,他左右两侧的筷子都是可以使用的,没有被占用,但是如果他想进食,因为4号哲学家还在占用着mutex并被阻塞,所以他在执行P(mutex)时会被阻塞。明明有临界资源可以用,但是却不能访问,这违背了空闲让进原则,这种方法的并发度不够高。

最多允许四个哲学家同时拿起筷子

只要最多四个哲学家同时拿起筷子,就必然有至少一个哲学家可以拿起一双筷子,避免了死锁。

为了解决这个问题,我们可以把上个方法中,mutex的初始值设置为4。这样,在上一情况中,4号哲学家依然会被右手的筷子阻塞,但2号哲学家不会被mutex阻塞,可以正常进食。

同时,在极端情况,即每个哲学家进程拿起一根筷子就被调度的情况中,最后一个进程想要拿起筷子时,会因为mutex已经为0,在P(mutex)步骤中被阻塞,从而在不占用任何筷子的情况下进入阻塞态,这样就不会发生死锁。

小结:

哲学家进餐问题的关键在于避免死锁,每个进程都需要持有两个临界资源,因此就有了“死锁”的隐患,想避免死锁,就要尽量让进程在不占用临界资源的情况下进入阻塞态。


感谢您看到这里,如果满意的话麻烦您点个赞支持一下,个人主页还有更多内容分享。

个人能力不足,如有错漏部分还请指出,我会尽快修改。

内容总结自王道计算机考研《操作系统》 和 人民邮电出版社《操作系统导论》

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

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

相关文章

如何在vue的项目中导入阿里巴巴图标库

阿里巴巴矢量图标库官网:iconfont-阿里巴巴矢量图标库 选择你喜欢的图标,添加入库 点击添加至项目,并新建文件夹,点击确定 选择font-class,点击生成代码 代码生成后,在网站上打开 全选复制到style 点击复制…

Agents 要点

一、Agents概念 人类是这个星球上最强大的 Agent。Agent是一个能感知并自主地采取行动的实体,这里的自主性极其关键,Agent要能够实现设定的目标,其中包括具备学习和获取知识的能力以提高自身性能。 关键点:感知环境、自主决策、具…

SpringBoot新手快速入门系列教程十一:基于Docker Compose部署一个最简单分部署服务项目

如果您还对于Docker或者Docker Compose不甚了解,可以劳烦移步到我之前的教程: SpringBoot新手快速入门系列教程九:基于docker容器,部署一个简单的项目 SpringBoot新手快速入门系列教程十:基于Docker Compose&#xf…

CSS特效:pointer-events: none;的一种特殊应用

一、需求描述 今天看到一个设计需求:需要在弹框中显示如下界面,其中有两个效果: 1.顶部点击项目,下面的内容能相应滚动定位,同时滚动的时候顶部项目也能相应激活显示 2.顶部右侧有一个模糊渐变效果,并且要…

day29--452. 用最少数量的箭引爆气球+435. 无重叠区间+763.划分字母区间

一、452. 用最少数量的箭引爆气球 题目链接:https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/ 文章讲解:https://programmercarl.com/0452.%E7%94%A8%E6%9C%80%E5%B0%91%E6%95%B0%E9%87%8F%E7%9A%84%E7%AE%AD%E5%BC%95%E7%88…

ISO/OIS的七层模型②

OSI模型是一个分层的模型,每一个部分称为一层,每一层扮演固定的角色,互不干扰。OSI有7层,从上到下分别是: 一,每层功能 7.应用层(Application layer ):应用层功能&#x…

AI克隆声音,基于函数计算部署GPT-Sovits语音生成模型

阿里云的 https://developer.aliyun.com/adc/scenario/808348a321844a62b922187d89cd5077 还是 函数计算 FC (aliyun.com) 选择 语音克隆生成 GPT-SOVITS 通过访问域名就能访问 就可以上传个人的声音,然后进行输出 。

【第29章】MyBatis-Plus之分页插件

文章目录 前言一、支持的数据库二、配置方法三、属性介绍四、自定义 Mapper 方法中使用分页五、其他注意事项六、Page 类七、实战1. 配置类2. 分页类3. 测试 总结 前言 MyBatis-Plus 的分页插件 PaginationInnerInterceptor 提供了强大的分页功能,支持多种数据库&a…

SpringBoot3+Vue3开发园区管理系统

介绍 在当今快速发展的城市化进程中,高效、智能的园区管理成为了提升居民生活品质、优化企业运营环境的关键。为此,我们精心打造了全方位、一体化的园区综合管理系统,该系统深度融合了园区管理、楼栋管理、楼层管理、房间管理以及车位管理等…

openlayers WebGL裁剪图层,双图层拼接显示

本篇介绍一下使用openlayers WebGL裁剪图层,双图层拼接显示 1 需求 WebGL裁剪图层,双图层拼接显示 2 分析 图层prerender和postrender事件的使用 WebGL scissor方法的使用 scissor方法指定了一个裁剪区域,用来将绘图区域限制在其限定的盒…

深度学习中的超参管理方法:argparse模块

在深度学习方法中我们不可避免地会遇到大量超参数如(batch_size、learning_rate等)。不同的超参数组合可以得到不同的训练/测试结果。所以在训练和测试过程中我们需要不断调整超参数获得理想的结果(炼丹),如果每一次去…

设备管理中的数据结构

一、有哪些数据结构属于设备管理数据结构 1. 设备控制表(DCT) “Device Control Table”的首字母缩写 2. 控制器控制表(COCT) “Controller Of Control Table”的首字母缩写。 3. 通道控制表(CHCT) “…

简单实现一个本地ChatGPT web服务(langchain框架)

简单实现一个本地ChatGPT 服务,用到langchain框架,fastapi,并且本地安装了ollama。 依赖安装: pip install langchain pip install langchain_community pip install langchain-cli # langchain v0.2 2024年5月最新版本 pip install bs4 pi…

基于swagger插件的方式推送接口文档至torna

目录 一、前言二、登录torna三、创建/选择空间四、创建/选择项目五、创建/选择应用六、获取应用的token七、服务推送7.1 引入maven依赖7.2 test下面按照如下方式新建文件 一、前言 Torna作为一款企业级文档管理系统,支持了很多种接口文档的推送方式。官方比较推荐的…

基于EMQX+Flask+InfluxDB+Grafana打造多协议物联网云平台:MQTT/HTTP设备接入与数据可视化流程(附代码示例)

摘要: 本文深入浅出地介绍了物联网、云平台、MQTT、HTTP、数据可视化等核心概念,并结合 EMQX、Flask、InfluxDB、Grafana 等主流工具,手把手教你搭建一个支持多协议的物联网云平台。文章结构清晰,图文并茂,代码翔实易懂&#xff0…

Linux 入门教程 by 程序员鱼皮

本文作者:程序员鱼皮 免费编程学习 - 编程导航网:https://www.code-nav.cn 大家好,我是鱼皮。 前两天我学编程的老弟小阿巴过生日,我问他想要什么礼物。 本来以为他会要什么游戏机、Q 币卡、鼠标键盘啥的,结果小阿巴…

网关、DHCP协议、ip地址、子网掩码简单介绍

参考文章:https://baike.baidu.com/item/%E7%BD%91%E5%85%B3/98992?frge_ala https://baike.baidu.com/item/DHCP%E6%9C%8D%E5%8A%A1%E5%99%A8/9956953?fromModulelemma_inlink https://blog.csdn.net/weixin_58783105/article/details/135041342 https://blog.cs…

Spring系列三:基于注解配置bean 下

基于注解配置bean 💗自动装配🍝案例1: Autowired引出🍝案例2: Autowired解读🍚案例3: Resource解读🍝小结 💗泛型依赖注入🍝基本说明🍝应用实例 💗自动装配 ●基本说明 …

数据中心巡检机器人助力,河南某数据中心机房智能化辅助项目交付

随着数据中心规模的不断扩大和业务需求的不断增长,确保其高效、安全、稳定地运行变得愈发重要。传统的人力巡检方式存在效率低、误差高、成本大等问题,难以满足现代数据中心的需求。为解决这些挑战,智能巡检机器人应运而生,成为数…

应用最优化方法及MATLAB实现——第3章代码实现

一、概述 在阅读最优方法及MATLAB实现后,想着将书中提供的代码自己手敲一遍,来提高自己对书中内容理解程度,巩固一下。 这部分内容主要针对第3章的内容,将其所有代码实现均手敲一遍,中间部分代码自己根据其公式有些许的…