操作系统(8)---进程的同步与互斥以及信号量机制(万字总结~)(4)

简介: 操作系统(8)---进程的同步与互斥以及信号量机制(万字总结~)

操作系统(8)---进程的同步与互斥以及信号量机制(万字总结~)(3):https://developer.aliyun.com/article/1511049

读者、写者问题中有一个潜在的问题:


只要有读进程还在读,进程就要一直阻塞等待,可能"饿死"。因此,这种算法中,读进程是优先的


例如,第一个进程到来后,执行P(rw)操作,使得rw由1变为0,那么写进程的P(rw)操作就会堵塞等待,若此时源源不断有读进程执行读操作,那么写进程就会一直等待。

我们可以再设置一个信号量,用于直线"写优先"

1.若两个读者之间并发运行:

读者1与读者2是并发运行的,即宏观上同时读,但是微观上读者1先执行读操作,再读者2进行读操作,若读者1执行到P(rw)操作后,切换到读者2进程,那么读者2进程会被堵塞再P(w)

直到读者1执行完V(w),读者2才能继续执行,由于这里会跳过if语句,所以读进程是能并发运行的。


2.两个写者进程并发运行:


第一个写进程没有执行完之前,第二个写进程会被堵塞再P(w),直到第一个写进程执行完V(w)之后,第二个写进程才能继续执行,两个写进程是能实现互斥访问的。


3.一个写者和一个读者并发运行:


(1)写者-->读者


写进程在写文件时,w的信号量已经由1变为0,此时读者进程会被堵塞在P(w),直到写进程执行完V(w),读者进程才有可能访问共享文件。实现了"写优先"。


(2)读者1--->写者1--->读者2


1.读者进程在读完文件后w值为1,写者进程能执行P(w),w的值由1变为0,但是写者进程会被堵塞在P(rw)


2.若此时还有读者想要读共享文件,由于w的值为0,所以会被堵塞在P(w)中


3.那么写者1和读者2都会堵塞,当读者读完文件后,count--,因为只有1个读者读文件,所以count--后,count值为0,那么就会执行V(rw)


4.这样就可以唤醒堵塞在P(rw)的写者进程了,实现了"写优先"

(3)写者1-->读者1-->写者2

1.当写者写文件时,执行P(w)和P(rw),那么w=0,rw=0


2.读者在读文件时,就会堵塞在P(w)


3.若写者2也想写进程的话,那么第二个写者进程也会被阻塞在P(w)中。


4.由于读者先对w进行了P操作,所以读者1在等待队列的对头位置,而写者进程2排在后面,所以当写者1执行完V操作后,唤醒的是读者进程。

结论:


在这种算法中,连续进入的多个读者可以同时读文件;写者和其他进程不能同时访问文件;写者不会饥饿,但也并不是真正的“写优先”,而是相对公平的先来先服务原则。有的书上把这种算法称为"读写公平法"。


4.哲学家进餐问题

一张圆桌上坐着5名哲学家,每两个哲学家之间的桌上摆一根筷子,桌子的中间是一碗米饭。哲学家们倾注毕生的精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿时,才试图拿起左、右两根筷子(一根一根地拿起)。如果筷子已在他人手上,则需等待。饥饿的哲学家只有同时拿起两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考。

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


2.整理思路。这个问题中只有互斥关系,但与之前遇到的问题不同的是,每个哲学家进程需要同时持有两个临界资源才能开始吃饭。如何避免临界资源分配不当造成的死锁现象,是哲学家问题的关键。


3.信号量设置。定义互斥信号量数组chopstick[5]={1,1,1,1,1},用于实现对5个筷子的互斥访问。并对哲学家按0~4编号,哲学家i左边的筷子编号为i,右边的筷子编号为(i+1)%5。

这里设置每个哲学家吃饭前依次拿起左、右两支筷子。

若此时5个哲学家并发地拿起了自己左手边的筷子,每位哲学家循环等待右边的人放下筷子(阻塞)

发生“死锁。

如何避免死锁:

①可以对哲学家进程施加一些限制条件,比如最多允许四个哲学家同时进餐。这样可以保证至少有一个哲学家是可以拿到左右两只筷子的(剩余的筷子分配给相邻的哲学家即可)。

②要求奇数号哲学家先拿左边的筷子,然后再拿右边的筷子,而偶数号哲学家刚好相反。用这种方法可以保证如果相邻的两个奇偶号哲学家都想吃饭,那么只会有其中一个可以拿起第一只筷子,另一个会直接阻塞。这就避免了占有一支后再等待另一只的情况。


③仅当一个哲学家左右两支筷子都可用时才允许他抓起筷子。

(1)若1号哲学家执行P(mutex)操作,使mutex由1变为0,继续执行P(chopstick[i]),此时

若切换为2号哲学家,此时2号哲学家会被堵塞在P(mutex)中,只有等待1号哲学家执行完P(chopstick[(i+1)%5])后,拿起右边筷子后,才能释放mutex。

(2)若0号哲学家执行完以下操作,就会释放mutex

此时1号哲学家可以通过P(mutex),但是因为0号拿了两支筷子,所以1号哲学家会被堵塞在P(chopstick[i])


而2号哲学家由于1号哲学家已经执行了P(mutex),所以mutex由1变为0,所以2号哲学家又会被阻塞在P(mutex),所以即使2号哲学家左右两边筷子都可以用,但是依然会被堵塞。


因此这种方法并不能保证只有两边的筷子都可用时,才允许哲学家拿起筷子。

(3)0号哲学家拿起左右两边的筷子开始吃饭,此时4号哲学家可以拿起左边的筷子,但是不能拿右边的筷子,即会被阻塞在P(chopstick[(i+1)%5])

因此此时4号右边的筷子不可用,但4号仍然会先拿起左边的筷子



总结:


各哲学家拿筷子这件事必须互斥的执行。这就保证了即使一个哲学家在拿筷子拿到一半时被阻塞,也不会有别的哲学家会继续尝试拿筷子。这样的话,当前正在吃饭的哲学家放下筷子后,被阻塞的哲学家就可以获得等待的筷子了。

哲学家进餐问题的关键在于解决进程死锁。


这些进程之间只存在互斥关系,但是与之前接触到的互斥关系不同的是,每个进程都需要同时持有两个临界资源,因此就有“死锁”问题的隐患。若遇到了一个进程需要同时持有多个临界资源的情况,应该参考哲学家问题的思想,分析题中给出的进程之间是否会发生循环等待,是否会发生死锁。


四.管程

1.为什么要引入管程

由于信号量机制存在编写程序困难、易出错的问题,例如:

像这样如果写错了P操作的顺序,按 ①②③执行,就会发生死锁

所以设置了管程,使程序员写程序时不需要关注复杂的PV操作

2.管程的定义和基本特征

管程是一种特殊的软件模块,有这些部分组成:

1.局部于管程的共享数据结构说明;

2.对该数据结构进行操作的一组过程(函数)

3.对局部于管程的共享数据设置初始值的语句;

4.管程需要有一个名字。

为了用管程实现进程间的互斥和同步,管程需要以下特征:

1.局部于管程的数据只能被局部于管程的过程所访问;

2.一个进程只有通过调用管程内的过程才能进入管程访问共享数据;

3.每次仅允许一个进程在管程内执行某个内部过程。

3.管程的例子

1.用管程解决生产者消费者问题


①生产者只用将函数的参数传入ProducerConsumer.insert();其他互斥同步等问题都由管程解决。


②消费者也可以直接调用函数,从缓存中取出产品,中间的过程只由管程负责。


由编译器负责实现各进程互斥进入管程的过程,生产者进程、消费者进程只需要调用管程中的函数就可以了

1.实现进程互斥

若第一个进程正在调用insert函数,若第二个进程也想调用,编译器就会阻止第二个进程使用该函数。只有第一个进程使用完毕,第二个进程才能使用。


2.实现进程同步


管程中会设置条件变量和等待/唤醒操作,用来解决同步问题,例如:


消费者1想要从缓冲区取产品,但是初始count==0,所以第一个消费者进程会等待在empty这一条件变量的队列中,消费者2进程同理

生产者生产产品后,会将产品放入缓冲区中,并且检查count是否为1,即自己放入的产品是不是缓冲区中的第一个产品,这就意味着可能由消费者进程在等待产品,所以此时会用signal(empty),唤醒在empty条件变量中等待产品的进程,在这里会先唤醒对头进程,也就是消费者1。

若执行消费者进程后,count--,此时count==N-1,那么就说明原本缓冲区是满的,现在消费者取走一个进程,那么生产者就可以继续生产了,就会使用signal(full),唤醒生产者进程。

总结:

引入管程的目的无非就是要更方便地实现进程互斥和同步。

1.需要在管程中定义共享数据(如生产者消费者问题的缓冲区)

2.需要在管程中定义用于访问这些共享数据的“入口”----其实就是一些函数(如生产者消费者问题中,可以定义一个函数用于将产品放入缓冲区,再定义一个函数用于从缓冲区取出产品)


3.只有通过这些特定的“入口”才能访问共享数据


4.管程中有很多“入口”,但是每次只能开放其中一个“入口”,并且只能让一个进程或线程进入(如生产者消费者问题中,各进程需要互斥地访问共享缓冲区。管程的这种特性即可保证一个时间段内最多只会有一个进程在访问缓冲区。


注意:这种互斥特性是由编译器负责实现的,程序员不用关心


5.可在管程中设置条件变量及等待/唤醒操作以解决同步问题。可以让一个进程或线程在条件变量上等待(此时,该进程应先释放管程的使用权,也就是让出“入口”);可以通过唤醒操作将等待在条件变量上的进程或线程唤醒。


程序员可以用某种特殊的语法定义一个管程(比如:monitor ProducerConsumer…end monitor)之后其他程序员就可以使用这个管程提供的特定“入口”很方便地使用实现进程同步/互斥了。即程序设计中封装的思想(将复杂的细节隐藏,对外提供简单易用的接口)。

java中类似管程的机制:

Java 中,如果用关键字 synchronized 来描述一个函数,那么这个函数同一时间段内只能被一个线程调用

每次只能有一个线程进入insert 函数,如果多个线程同时调用 insert 函数,则后来者需要排队等待。

目录
相关文章
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
407 32
|
10月前
|
缓存 运维 前端开发
|
10月前
|
缓存 运维 前端开发
阿里云操作系统控制台:高效解决性能瓶颈与抖动之进程热点追踪
遇到“进程性能瓶颈导致业务异常”等多项业务痛点时,提供高效解决方案,并展示案例。
|
监控 搜索推荐 开发工具
2025年1月9日更新Windows操作系统个人使用-禁用掉一下一些不必要的服务-关闭占用资源的进程-禁用服务提升系统运行速度-让电脑不再卡顿-优雅草央千澈-长期更新
2025年1月9日更新Windows操作系统个人使用-禁用掉一下一些不必要的服务-关闭占用资源的进程-禁用服务提升系统运行速度-让电脑不再卡顿-优雅草央千澈-长期更新
1944 2
2025年1月9日更新Windows操作系统个人使用-禁用掉一下一些不必要的服务-关闭占用资源的进程-禁用服务提升系统运行速度-让电脑不再卡顿-优雅草央千澈-长期更新
|
消息中间件 Linux
Linux:进程间通信(共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
通过上述讲解和代码示例,您可以理解和实现Linux系统中的进程间通信机制,包括共享内存、消息队列和信号量。这些机制在实际开发中非常重要,能够提高系统的并发处理能力和数据通信效率。希望本文能为您的学习和开发提供实用的指导和帮助。
882 20
|
6月前
|
Ubuntu Unix Linux
操作系统的最强入门科普(Unix/Linux篇)
下期文章,小枣君会重点聊聊Windows和macOS那条线。敬请关注! 如果大家觉得文章不错,还请帮忙多多转发!谢谢!
|
6月前
|
Web App开发 缓存 Rust
|
安全 Linux 数据安全/隐私保护
Vanilla OS:下一代安全 Linux 发行版
【10月更文挑战第30天】
859 0
Vanilla OS:下一代安全 Linux 发行版
|
12月前
|
运维 自然语言处理 Ubuntu
OS Copilot-操作系统智能助手-Linux新手小白的福音
OS Copilot 是阿里云推出的一款操作系统智能助手,专为Linux新手设计,支持自然语言问答、辅助命令执行和系统运维调优等功能。通过简单的命令行操作,用户可以快速获取所需信息并执行任务,极大提升了Linux系统的使用效率。安装步骤简单,只需在阿里云服务器上运行几条命令即可完成部署。使用过程中,OS Copilot不仅能帮助查找命令,还能处理文件和复杂场景,显著节省了查找资料的时间。体验中发现,部分输出格式和偶尔出现的英文提示有待优化,但整体非常实用,特别适合Linux初学者。
530 10
|
弹性计算 自然语言处理 Ubuntu
OS Copilot-操作系统智能助手-Linux新手小白的福音
OS Copilot是由阿里云推出的操作系统智能助手,专为Linux新手设计,支持自然语言问答、辅助命令执行等功能,极大提升了Linux系统的使用效率。用户只需通过简单的命令或自然语言描述问题,OS Copilot即可快速提供解决方案并执行相应操作。例如,查询磁盘使用量等常见任务变得轻松快捷。此外,它还支持从文件读取复杂任务定义,进一步简化了操作流程。虽然在某些模式下可能存在小问题,但总体上大大节省了学习和操作时间,提高了工作效率。
428 2
OS Copilot-操作系统智能助手-Linux新手小白的福音

推荐镜像

更多