| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Python知识库 -> 进程同步、互斥与信号量 -> 正文阅读 |
|
[Python知识库]进程同步、互斥与信号量 |
进程同步由于进程调度的存在,进程具有异步性的特征,即并发执行的进程以各自独立、不可预知的速度向前推进,进程之间没有明确的先后执行顺序。 而像管道通信这种进程通信方式,分别写数据和读数据的两个进程必须按照“写数据->读数据”的顺序来执行,这就是进程同步。同步亦称直接制约关系,指多个进程在某些位置上协同(制约)它们的工作次序,以实现进程之间的合作。 进程互斥进程并发执行不可避免地需要共享一些资源(比如内存数据、打印机)。资源有两种共享方式:互斥共享和同时共享。互斥共享是一个时间段只能有一个进程访问该资源,同时共享是一个时间段允许多个进程"(宏观上)同时”访问该资源。 临界资源:一个时间段内只允许一个进程使用的资源。对临界资源的访问必须互斥地进行。互斥,亦称间接制约关系,当一个进程访问完临界资源后另一个进程才能访问该资源,否则另一个进程就需要等待。 对临界资源的互斥访问,可以在逻辑上分为如下四个部分:
进入区和退出区是对临界区的保护,即实现互斥的代码段。进入区和退出区对临界区进行上锁和解锁,以此来实现对临界区的互斥访问。而设计进程互斥(上锁和解锁的过程)算法就是设计进入区和退出区。其目标是让进入区和退出区能对临界区进行保护,简单的方法就是让进入区和退出区的代码都是原子操作(一气呵成,不发生中断,即不发生进程调度),复杂的方法就是设计算法让进程调度不打乱上锁和解锁的过程。 对临界资源的访问(进程互斥),应遵循一下原则:
进程互斥的实现应当必须遵循前3条原则,第4条属于锦上添花。 进程互斥的软件实现方法软件实现就是设计算法,让进程调度的存在不打乱上锁和解锁过程。
算法思想:临界区权限在进程间转移,每个进程进入临界区的权限只能被另一个进程赋予。
turn的初值为0,刚开始只允许0号进程进入临界区。若P1先上处理机运行,则会一直卡在进入区,直到P1的时间片用完,发生调度,切换P0上处理机运行。P0访问临界区时,切换会P1,P1仍然卡在进入区。 该算法可以实现 “同一时刻最多只允许一个进程访问临界区”,缺点是:如果P0不访问临界区,P1就永远不能访问临界区,违背了“空闲让进”原则。
上面讲了单标志法,实际上还有双标志先检查法、双标志后检查法,这里就不展开了,快进到Peterson算法。Peterson算法是将单标志法和双标志法的融合改进。 算法思想:如果两个进程都想进入临界区,可以让进程尝试“孔融让梨”,主动让对方先使用临界区,且将最后一次“让梨”视为有效。比如A和B都想洗澡,A说B先洗,B说A先洗,则A先洗。
上面#1 #2 #3是进入区,#4是临界区,#5是退出区。在这种互斥算法下,无论是什么情况的进程调度,都可以保证只有一个进程进入临界区。 如果执行顺序是 如果执行顺序是#1623…,经过#162后,flag==[true, true], turn == 1,此时P0让梨给P1,P0不会进入临界区而是忙等待,等到进程切换到P1,执行#7,这是P1又让梨给P0,P1就不会进入临界区,等到下次进程切换到P0,P0进入临界区,P0退出临界区后,进程切换到P1,P1就可以进入临界区。 对于任意的执行顺序,Peterson算法都可以有效地实现进程互斥,过程就如上面分析的。不过Peterson算法是靠CPU空转来实现等待,处理机属于忙等待状态,没有满足“让权等待”原则。 进程互斥的硬件实现方法硬件实现的方法主要是让操作变成原子操作。
信号量机制上面不管是软件实现还是硬件实现进程互斥,都多多少少有些缺陷,因此现在更多地是使用信号量机制来实现进程同步、互斥。1965年,荷兰学者Dijkstra提出了信号量机制,以实现进程互斥、同步。信号量用来表示某种资源的数量。 用户进程可以通过操作系统提供的一对原语来对信号量进行操作。原语是一种特殊的程序段,其执行只能一气呵成,不可被中断。这一对原语是
当
缺点:不满足“让权等待”,发生忙等待。 信号量的保护信号量机制运行的过程:
上面说过,P和V操作是一对原语,这实际上是对信号量的保护。信号量是一种被多个进程共享的资源,也是一种临界资源,因此对信号量的修改(PV操作)必须是互斥的,同一时间只能有一个进程对信号量进行修改,这就是对信号量的保护,也就是对信号量的修改必须是原语。让PV操作变成原语(对信号量的保护)可以采用互斥的软/硬件实现方法。最简单的方法就是开关中断(仅适用于单核CPU)或者TS指令(信号量给临界资源上锁,TS指令再给信号量上锁),使信号量的修改变成原语。 信号量-同步:初值为0对于一个资源的使用和释放,不同的进程之间有明确先后关系,比如生产者产生数据,消费者才能使用数据。具体如下:
P和V组成一对,分别在不同的进程中,先执行的进程为P,后执行的进程为V。 考虑进程同步主要依靠以下步骤:
信号量-互斥:初值为1对于一个共享资源的读写,不同进程间是互斥的,这时使用初值为1的互斥信号量(也称为互斥锁),实现进程互斥。 这里可以将临界区看做是数量为1的资源,因此初值为1。
PV组成一对,在同一个进程中,要读写共享资源前先P一下,看是否有人用。读写完再V一下释放这个资源。P和V来包裹读写资源的操作,读写临界资源的代码放在临界区。 思考步骤:
信号量的应用1 生产者-消费者问题问题描述:生产者-消费者模型是有一个有上限的共享数据区(缓冲区)存放数据,有一个生产者进程生产数据,有一个消费者进程消费数据,目标是让生产者和消费者能够协同地生产数据和消费数据,即生产者和消费者合作。 解:对生产者来说,其资源是空闲缓冲区。如果存在空闲缓冲区,则生产者工作;如果不存在空闲缓冲区,则生产者等待。而对消费者来说,其资源是非空闲缓冲区。对缓冲区的操作应当是互斥的 生产者工作前P一下自己的资源,看看有没有空闲缓冲区,工作完成后V一下消费者的资源(唤醒消费者)。消费者工作前P一下自己的资源,工作完成V一下生产者(唤醒生产者)。由于要操作PCB,因此P和V要做成系统调用,在内核态运行。 这里采用文件当做共享缓冲区,定义一个互斥信号量
2 吸烟者问题问题描述:假设一个进程有三个吸烟者进程和一个供应者进程。每个吸烟者不停地卷烟并吸掉它,但是卷烟需要三种材料:a,b,c。三个吸烟者中,第一个拥有无限的b和c,第二个拥有无限的a和c,第三个拥有无限的a和b。供应者无限地提供三个材料中的一种,并把材料放在桌子上,缺少该种材料的吸烟者会拿起材料并卷起来吸掉它,并告诉供应者完成了该次吸烟,供应者就会再在桌子上放一种材料,这个过程一直重复且三个吸烟者轮流吸烟。 解:进程顺序:供应者->吸烟者1->供应者->吸烟者2->供应者->吸烟者3->供应者->… 每个箭头都应当是一个信号量,三个吸烟者需要的资源是三种不同的材料,用三个信号量表示缓冲区中的材料是哪种。吸烟者用完缓冲区的材料后通知供应者,即供应者的资源是空的缓冲区,或者定义成是否吸烟完成。桌子(缓冲区)的读写应当是互斥的,用一个互斥信号量
刚开始 另外可以发现,任何时刻都只有一个进程修改桌子(缓冲区),缓冲区的互斥锁其实是可以省略的。 3 读者-写者问题问题描述:有读者和写者两组并发进程,共享一个文件。要求:
解:进程间没有先后顺序,读者进程之间没有限制,写者进程之间是互斥的,读者进程和写者进程是互斥的。关键就在于读者进程和写者进程互斥的处理。 写者进程可以用一个互斥锁来实现互斥;写者和读者之间就不能用互斥锁解决,如果用互斥锁,读者和读者之间就没办法同时读了。可以用一个整数变量来记录一个文件有几个读者进程,如果这个变量大于零,就不能写,另外这个变量要用一个锁来保护,虽然读写这个变量的操作不是原子的,但是可以保证一个进程读写过程中其他进程不能读写,这样进程调度时就不会出现问题。
分析:如果进程的到来顺序是如下情况:
改进:在上述情况中,读者的优先级是高于写者的,根本原因是在于不管有没有写者,读者都不会被写者阻塞,因此添加一个写者对读者的锁,让有写者进程等待时,新来的读者要等待在写者后面。
当没有写者时,w对读者没有阻塞作用,而当有写者进程时,w就会阻塞新来的读者进程。 4 哲学家进餐问题问题描述:一张圆桌坐着5名哲学家,每两名哲学家之间摆放一根筷子,哲学家只处于思考和饥饿两种状态。当哲学家饥饿时,会试图拿起左右两根筷子(一根一根地拿起)。如果筷子在别人手上,就需要等待。只要饥饿的哲学家拿起两根筷子才可以开始吃饭,吃完后放下筷子继续思考。 解:五个哲学家进程和五根筷子资源,每两个哲学家对其中间的筷子的访问是互斥的,每个哲学家只能访问到相邻的两根筷子,所以定义5个信号量表示筷子的占用。
分析:上面程序是有问题的,假如恰好每个哲学家进程执行完 改进:如何防止死锁的发生,有以下几种方案:
与之前的问题不同的是,哲学家问题中,一个进程需要同时持有两个以上临界资源,因此就有“死锁”的隐患。当遇到这种情况,可以参考上面三种方案。 死锁死锁是指:在并发环境下,各进程因竞争资源而造成的相互等待对方手中的资源(你等待我,我等待你),导致各进程都阻塞,都无法向前推进的现象。发生死锁后若无外力干扰,这些进程都无法向前推进。 要注意,使用PV操作时,如果顺序不对,很容易造成死锁。 死锁和饥饿的区别死锁一定是“循环等待对方手里的资源”导致的,如果发生死锁,一定是两个或两个以上的进程同时发生死锁,死锁进程一定处于阻塞态。 **饥饿是长期得不到想要资源而进程无法向前推进的情况,发生饥饿的进程可能只有一个,且可能处于阻塞态,也可能处于就绪态。**饥饿与资源分配策略有关,比如读者-写者问题中读者比写者优先导致写者饥饿,再比如短作业优先(SPF)算法中,短作业进程源源不断到来导致长作业进程一直得不到处理机而发生饥饿。 死锁产生的必要条件产生死锁必须同时满足以下四个必要条件。满足必要条件并且进程调度产生某种会发生死锁的进程执行顺序时,才会发生死锁。
死锁的处理策略
每种策略的具体方法就不具体展开了,死锁的发生概率并不高并且不容易处理,前三种方法都有弊端。许多通用操作系统如PC机的Windows和Linux都采用死锁忽略的方法,直接重启。 管程(monitor)信号量机制编写程序困难、易出错,因此就有了管程。管程保证同一时刻只有一个进程在管程内活动(由编译器实现),并且需要设置 管程可以视为一个线程安全的数据结构,其内部提供了互斥与同步操作,向外提供访问共享数据的专用接口(称为管程的过程),通过管程提供的接口即可达成共享数据的保护与线程间同步。 使用管程,可以简化线程间互斥、同步的编码复杂度(否则需自己控制互斥、同步机制,并保证正确),可以集中分散的互斥、同步操作代码,更容易验证、查错,也更可读(反之,信号量的PV操作可能分散到各个地方,验证、阅读相对麻烦),这个层面来说管程是一种封装。 管程包含以下部分:
管程的特点
生产者-消费者问题用管程解决生产者-消费者问题。要注意,管程不是简单的对函数进行封装,是要编译器负责实现进程互斥地进入管程。
Java中类似管程的机制Java中,用关键字
用C++实现管程可以参考这篇文章: 参考: 操作系统(哈工大李治军老师)32讲(全)超清_哔哩哔哩_bilibili 进程的同步、互斥、通信的区别,进程与线程同步的区别_jyh的博客-CSDN博客_进程同步 操作系统PV操作考研期末考试专用_哔哩哔哩_bilibili |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/15 16:27:47- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |