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

学习日期: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=1writer(){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.mzph.cn/diannao/45201.shtml

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

相关文章

解决POST请求中文乱码问题

解决POST请求中文乱码问题 1、乱码原因2、解决方法3、具体步骤 💖The Begin💖点点关注,收藏不迷路💖 在Web开发中,处理POST请求时经常遇到中文乱码问题,这主要是由于服务器在接收到POST请求的数据后&#x…

BUG解决:postman可以请求成功,但Python requests请求报403

目录 问题背景 问题定位 问题解决 问题背景 使用Python的requests库对接物联数据的接口之前一直正常运行,昨天突然请求不通了,通过进一步验证发现凡是使用代码调用接口就不通,而使用postman就能调通,请求参数啥的都没变。 接口…

SSL 证书错误:如何修复以及错误发生的原因

SSL证书可以提升网站的可信度。然而,如果您的SSL证书出现错误,您可能会得到一个“不安全”的标签,这可能会导致访问者失去对您网站的信任并转向竞争对手。 本文将介绍SSL证书错误的原因及其对用户的潜在影响。随后,我们将提供详细…

MybatisPlus 核心功能

MybatisPlus 核心功能 文章目录 MybatisPlus 核心功能1. 条件构造器1.1 QueryWrapper1.2 LambdaQueryWrapper(推荐)1.3 UpdateWrapper1.4 LambdaUpdateWrapper 2. 自定义SQL3. Service接口 1. 条件构造器 当涉及到查询或修改语句时,MybatisP…

界面组件Kendo UI for React 2024 Q2亮点 - 生成式AI集成、设计系统增强

随着最新的2024年第二季度发布,Kendo UI for React为应用程序开发设定了标准,包括生成式AI集成、增强的设计系统功能和可访问的数据可视化。新的2024年第二季度版本为应用程序界面提供了人工智能(AI)提示,从设计到代码的生产力增强、可访问性…

Java毕业设计 基于SSM vue图书管理系统小程序 微信小程序

Java毕业设计 基于SSM vue图书管理系统小程序 微信小程序 SSM 图书管理系统小程序 功能介绍 用户 登录 注册 首页 图片轮播 图书信息推荐 图书详情 赞 踩 评论 收藏 系统公告 公告详情 用户信息修改 我的待还 图书归还 催还提醒 我的收藏管理 意见反馈 管理员 登录 个人中心…

绝地求生PUBG奇幻大乱斗怎么玩 奇幻大乱斗什么时候回归

《绝地求生》(PUBG) 是由韩国开发的一款战术竞技型射击类沙盒游戏,游戏中我们需要和队友组队乘坐飞机跳伞到达一座空岛,之后我们需要搜索一切我们可以用到的物资,之后我们于敌人进行对战,期间不断躲避毒圈的追击,最后当…

Redis+Caffeine 实现两级缓存实战

RedisCaffeine 实现两级缓存 背景 ​ 事情的开始是这样的,前段时间接了个需求,给公司的商城官网提供一个查询预计送达时间的接口。接口很简单,根据请求传的城市仓库发货时间查询快递的预计送达时间。因为商城下单就会调用这个接口&#xff…

防火墙安全策略及用户认证实验

目录 一、实验拓扑 二、实验要求 三、实验思路 四、实验配置 1、配置vlan 2、配置路由器、防火墙IP地址,划分区域 3、配置安全策略 ​策略一: 策略二: 策略三: 4、配置用户认证 策略一: 策略二&#xff1a…

WebGIS基础原理

该部分内容与部分插图、学习框架的主要参考的网站与博主如下(也趁机分享给大家): OSGeo开源WebGIS在线教程:http://webgis.cn/ OSGeo《地理信息系统原理》:https://www.osgeo.cn/gis-tutorial/index.html OSGeo《Pyth…

DSC主备归档报错

先看一个报错: 2024-07-10 22:12:21.725 [ERROR] database P0000003511 T0000000000000003696 rafil_list_overlap_consecutive_check failed, rfil(DMDATA/data/DSC02/arch/ARCHIVE_LOCAL1_0x57843343_EP1_2024-07-10_20-44-40.log)->next_seq(2901) > nex…

部署Harbor仓库

本章内容: 安装docker-ce部署harbor仓库上传和拉取 1.安装docker 1)拉取源码 yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 2)安装docker-ce yum -y install docker-ce 3&#…

C++入门到进阶(图文详解,持续更新中)

C入门到进阶(图文详解,持续更新中) 目录 C入门到进阶(图文详解,持续更新中) 数据 数据类型 基本数据类型/内置数据类型 C常用运算符 赋值运算符 关系运算符 逻辑运算符 杂项运算符 数据的本地化…

第二课:使用域名dns ping通www.baidu.com

需要一台dns服务器,实现域名解析,把对应的网址变为Ip地址。 首先按照之前博客的配置,自动分配给PC1和PC2的IP地址等相关配置。 然后增加一台server交换机连接到交换机上,配置好ip地址,192.168.1.100。在dnsServer中。…

7.11日学习打卡----初学Redis(六)

7.11日学习打卡 目录: 7.11日学习打卡一. redis事务事务的概念与ACID特性Redis事务三大特性Redis事务执行的三个阶段Redis事务基本操作 二. redis集群主从复制主从复制环境搭建主从复制原理剖析 哨兵监控哨兵监控环境搭建哨兵工作原理剖析 故障转移Cluster模式Clust…

c++初阶知识——类和对象(1)

目录 1.类和对象 1.1 类的定义 1.2 访问限定符 1.3 类域 2.实例化 2.1 实例化概念 2.2 对象大小 内存对齐规则 3.this指针 1.类和对象 1.1 类的定义 (1)class为定义类的关键字,Stack为类的名字,{}中为类的主体&#xf…

达梦数据库中的线程和进程

达梦数据库中的线程和进程 在达梦数据库中,线程和进程的概念与操作系统中的定义类似,但有一些特定的实现细节和用途。以下是达梦数据库中线程和进程的一些关键点: 进程(Process): 在达梦数据库中&#x…

mysql数据库被偷家,数据全部丢失。还勒索我给他比特币

歹徒留下的勒索信息。 解决办法: 1、设置ip白名单, 2、端口不要使用默认端口,随便换个端口就行。 3、密码设置复杂一点。

【Java16】多态

向上类型转换 对于引用变量,在程序中有两种形态:一种是编译时类型,这种引用变量的类型在声明它的时候就决定了;另一种则是运行时类型,这种变量的类型由实际赋给它的对象决定。 当一个引用变量的编译时类型和运行时类…

【Pytorch】Conda环境pack打包迁移报错处理

文章目录 Anaconda虚拟环境打包一、源电脑的环境打包1.安装conda-pack工具2.确定环境3.打包环境4.将打包环境拷贝到U盘 二、环境迁移到目标电脑上三、异常处理pip install -e. 导致无法pack→忽略管理的文件已经被删除或者被覆盖→压缩成tar注意 重新激活环境 Anaconda虚拟环境…