一、经典互斥、同步问题
1、生产者消费者问题
1)概念
说人话:
- 也就是对【同一数据缓存空间】
- 【输入数据的进程(生产者)】和【获取数据的进程(消费者)】需要互斥、同步的进入这个空间
其中:
- 【互斥】:同一时刻各进程(不管是生产者还是消费者),都只能互斥访问缓冲区这个临界资源,只允许一个进程进去
- 【同步】:
- 【生产者】要等缓冲区不满才可以存数进缓冲区,也就是要等【消费者】取数
- 【消费者】要等缓冲区不空才可以从缓冲区取数,也就是要等【生产者】存数
2)实际实现逻辑
提示,个人认为概念需要改一下,这里记住:
【关于同步问题】
- 【对于 "生产者" 视角】
![]()
- 【empty】=【未满的缓冲区的空间】,就是【可用的临界资源数量 】
- 初始值是N,因为还没存数进去还是空的,还有N个可用空间资源
- 当【生产者】存数时,就会使用【可用空间资源】,【empty--】【full++】
- 直到【empty<0(full=N)】,【生产者】没有【可用空间资源】了
- 【生产者】就需要**【消费者"V操作"】来释放** 【empty这个资源信号量】
- 【对于 "消费者" 视角】
![]()
- 【full】对消费者而言,【可取的数据(暂时当1个数据对应1个空间)】就是【可用的临界资源数量】
- 初始值是0,因为还没存数进去还是空的,消费者有0个数据资源可以取
- 当【消费者】取数时,就会使用【可取数据资源】,【full--】【empty++】
- 直到【full<0(empty=N)】,【消费者】没有【可取数据资源】了
- 【消费者】需要**【生产者"V操作"】来释放** 【full这个资源信号量】
【关于互斥问题】
- 【mutex】=【不管任何进程,控制所有进程可以访问资源的信号量】
- 互斥针对的所有进程,不管你是生产者还是消费者,一视同仁!!!!
- 那么做法就是跟之前学的【信号量实现"互斥"】一样:
- mutex初始值为1
- 在每一个进程里【P(mutex) ---> 使用资源 ---> V(mutex)】
【合起来完整代码】
注意:【互斥代码】只能被【同步代码】里包着
2、多生产者、多消费者
1)概念
就是4个原则:
- 1、【互斥】
- 依旧最基本的原则,同一时刻对"盘子"这个资源,不管是放水果、取水果,只能允许1个进程访问,因此需要互斥访问
- 2、【同步】
- 1)女儿要等父亲放苹果后,再取苹果
- 2)儿子要等母亲放橘子后,再取橘子
- 3)父母都要等儿子或女儿取走水果,盘子空了才可以放入水果
【提示】:
在分析这个问题的时候要做到:
- 1、分析每1个进程执行某个事件后,会引发其他进程干什么?
- 2、将这些关系整理后,一定要按照【事件】的形式分析
2)实际实现逻辑
【同步关系】:如图
- 对于如何【让儿子吃母亲的橘子,而不吃父亲的苹果】、【让女儿吃父亲的苹果,而不吃母亲的橘子】
- 最简单就是2个不同的信号量参数就行了,每对关系用各自的信号量控制约束
- 对于【让父母先等"儿子 or 女儿"取空盘子,再放水果】
- 就统一用plate这一个变量来控制约束就好了
- 别忘了同步操作是:前V后P(前操作后面V,后操作前面P)
;
;
【互斥关系】:如图
都是老套路了,我就不再多说
;
【注意!!】
- 多生产者、多消费者的一个特殊性质:
- 1、【缓冲区容量只有1时】:"有可能/可以"不设置互斥信号量mutex,也可以天然实现互斥
- 2、【缓冲区容量大于1时】:绝对不可以不设置互斥信号量mutex,不然会死锁
![]()
- 如果无法理解这个流程,那就记住这个性质就行了。。。。
3、读者、写者问题
1)概念
人话:
- 1、情况:有读者和写着两组进程并发访问一个【共享文件(数据空间)】
- 2、首先区分【读者】和【消费者】区别:
- 【消费者】读取数据的时候,会把数据直接拿走!!
- 【读者】只读不取,他不会动数据,只安安静静读,所以其实可以多个读者一起读
- 3、要做的原则是:
- 允许:
- 【多读者】:允许多个读者同时读,访问数据空间
- 不允许:
- 【1读1写】:1读进程 和 1写进程并发执行
- 【2写】:2个写进程同时
2)实际实现逻辑
【1.0版本】:解决简单的【一写一读互斥】
- 1、设置【rw】一个参数,用于一读一写2个进程之间互斥访问
- 2、解决了:【不允许1读1写】问题
- 3、但是出现:无法实现【多读者同时读】问题,因为mutex<0,在执行V操作前,其他所有读进程执行P操作都会被卡着
【2.0版本】:【一气呵成前提下解决 "多读进程同时"】
- 1、多一个参数信号量【count】,统计读进程数量
- (目前有rw、count两个参数)
- 2、写进程不变、读进程在P(rw)和V(rw)前后进行【count的检测】
- 这样做可以让【第一个进入的读进程 P(rw)上锁】、【最后一个结束的读进程 V(rw)解锁】
- 除了第一个读进程,其他读进程都可以跳过P(rw)操作之间读文件!!!
- 3、"有限制条件"地解决了【多进程同时读问题】
- 前提是:必须第一个读进程【P(rw)】和【count++】一气呵成,否则【其他读进程依旧count=0】
【3.0版本】:【读进程优先,写进程饿死】
- 1、再继续加参数信号量【mutex】,专门针对【count变量 的 "互斥"访问】
- (目前有rw、count、mutex 3个参数了)
- 2、写进程不变,读进程在【判断count】前后【包上P(mutex)、V(mutex)】
- 这样强制保证了只有第一个进程成功把【count++之后(V(mutex)解锁)】,第二个读进程才能获得【非0的count】
- 3、缺点就是【写进程饿死,读进程优先】
- 连续并发的读写操作下,只要不断有读进程进入,就要等很久才能让【最后一个读进程】去【V(rw)解锁】,【写进程】一直不能【P(rw)】使用
【4.0版本】:【读写公平(最完美版本)】
- 1、再加参数信号量【w】,强制让写进程不被读进程打扰
- (目前4个参数:rw、count、mutex、w)
- 2、写进程最外层包着【对w的PV互斥】、读进程在mutex前后包着【对w的PV互斥】
- 假设【读 ------> 写 ------> 读】
- 这样首先【读进程1:P(w)上锁】,【写进程】等;
- 【读进程1:V(w)解锁】,【写进程:P(w)上锁】,此时新的【读进程2】只能被迫等待写进程
- 直到【写进程:V(w)解锁】,【读进程2】才能用
- 而且因为写进程可以正常执行,因此【1个读进程内的 count变量】基本可以正常自己读完后count--=0,于是一个读进程【自己上锁、自己解锁】
最后完整代码逻辑:
3、哲学家问题
1)概念
官方描述
说人话,按我画的图片理解:
反正你不需要知道这个哲学家跟实际进程运行究竟有什么关系,因为没人知道,我也不知道,不知道哪个人才想出来的,记住下面的概念就行
然后问题就是:
- 1、【哲学家"吃饭"】需要用左右2个临界资源、【哲学家"思考"】不用资源
- 2、每个哲学家如何互斥使用左右资源,才能避免死锁?比如【0号哲学家拿了0、1两个筷子】,【1号哲学家想要1号筷子】,【4号哲学家想要0号筷子】咋办?
【初始版本1.0】
- 1、定义筷子资源的数组,然后在每一个哲学家进程里使用
- 2、每一个哲学家进程里有【吃饭】和【思考】
- 【吃饭】的时候要互斥访问
- 所以先【P(左筷子)】再【P(右筷子)】
- 然后【吃饭】
- 最后再依次【V(左筷子)】、【V(右筷子)】
- 【思考】就啥也不用,纯自己思考就行了
- 缺点:所有进程【P(右筷子)】时发生死锁:
- 因为每个【哲学家编号i】都不一样,对应的【P(左筷子i)】也不一样,所以所有进程并发的时候,互不影响都可以拿走左筷子资源
- 可是再【P(右筷子)】的时候就会发生死锁,所有进程互相等右边进程【V(左筷子)】,问题是没人能执行到这一步啊.....
2)实际实现逻辑
【第一种:官方写法】
没什么好说的,超级简单:
- 1、依旧沿用上面所有问题的解决方式:
- 一个进程的所有资源都拿到手之前,全部用mutex上锁:【添加semaohore mutex=1】
- 2、在一个进程【P(左筷子)】、【P(右筷子)】之前【P(mutex)加锁】,然后再【V(mutex)解锁】就好了
;
【第二种:复杂题骗分法】
网上一个博主教的,在遇到更复杂的题,所需资源不止【左筷子】、【右筷子】时,可以用此方法骗分
各位自己理解吧,实在不理解就背熟这个模板就好了
实际例题:
;
结合以上例子我们可以发现:
【哲学家吃饭】问题可以适用于解决各个进程所需资源不止一个的情况!!
4、总结
二、管程
1)是什么
人话:为了解决前面大量繁琐的【PV操作】,而统一设定的【处理同步互斥的接口函数】
无需程序员关心内部是如何实现互斥同步的逻辑,只需要之间调用函数就行
2)管程的结构组成(类似JAVA面向对象类)
- 解释混淆概念:【局部与管程内部的共享数据结构说明】VS【对局部于管程内部共享数据结构设置初始值的语句】
- 【局部与管程内部的共享数据结构说明】:
- 就是定义变量,没包含赋值(例:int a;)
- 【对局部于管程内部共享数据结构设置初始值的语句】:
- 给变量赋值了(例:a=0;)
对比JAVA的类:
例题:
3)管程特性
就记住:
解释:
- 1、也就是管程内的【局部变量】外部不可以修改访问,只能通过管程内的【特定函数】来修改、访问
- 2、管程【自身就是互斥的资源】!!!各个外部进程想用它,也得互斥使用!!!
- 3、注意:【管程】与【信号量机制】存在不同处
![]()
- 【前面学的PV操作】:
- 不仅更改进程的【阻塞、唤醒就绪状态】,还会【更改信号量值】
- 【管程】:
- 只更改进程的【阻塞、唤醒就绪状态】,不会【更改信号量值】!
- 例子:



















































