注意:对于不同的临界资源需要设置不同的互斥信号量
P、V操作必须成对出现。缺少P就不能保证临界资源的互斥访问。缺少V会导致资源永不被释放,等待进程永不被唤醒
进程同步:要让各并发进程按要求有序地推进
用信号量实现进程同步:
进程P1中有句代码S1,P2中有句代码S2,P3中有句代码S3。。。。这些代码要求按如下前驱图所示的顺序来执行
其实每一对前驱关系都是一个进程同步问题(需要保证一前一后的操作)
因此,
问题描述:
系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区取出一个产品并使用(注:这里的“产品”理解为某种数据)
生产者、消费这共享一个初始为空、大小为n的缓冲区
只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待
缓冲区没满-》生产者生产
只有缓冲区不空时,消费者才能从中取出产品,否则必须等待
缓冲区没空-》消费者消费
缓冲区是临界资源,各进程必须互斥地访问
互斥关系
缓冲区满时,生产者必须等待。
缓冲区空时,消费者必须等待。
PV操作题目分析步骤:
实现量进程的同步关系,是在其中一个进程执行P,另一进程中执行V
如果改变相邻P、V操作的顺序
例如mutex的p操作在前,且此时缓冲区内已经放满产品,则empty=0,full=n。按①②③的顺序执行
可能会引起生产者等待消费者释放空闲缓冲区,而消费者又等待生产者释放临界区的情况,生产者和消费者循环等待被对方唤醒,出现“死锁”。
同样的,若缓冲区中没有产品,即full=0,empty=n。按③④①的顺序执行就会发生死锁。
因此,实现互斥的P操作一定要在实现同步的P操作之后。V操作不会导致进程阻塞,因此两个V操作顺序可以交换。
易错点:实现互斥和实现同步的两个P操作的先后顺序(死锁问题)
问题描述:桌子上有一只盘子,每次只能向其中放入一个水果。爸爸转向盘子中放苹果,妈妈专向盘子中放橘子,儿子专等着吃盘子中的橘子,女儿专等着吃盘子中的苹果。只有盘子空时,爸爸或妈妈才可向盘子中放一个水果。仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出水果。
关系分析
互斥关系:对缓冲区的访问要互斥地进行
同步关系(一前一后):
“盘子为空”这个事件可以由儿子或女儿触发,事件发生后才允许父亲或母亲放水果
总结:在生产者-消费者问题中,如果缓冲区大小为1,那么有可能不需要设置互斥信号量就可以实现互斥访问缓冲区的功能。当然,这不是绝对的,要具体问题具体分析
建议:需要注意的是,实现互斥的P操作一定要在实现同步的P操作之后,否则可能引起“死锁”
解决“多生产者-多消费者问题”的关键在于理清复杂的同步关系
关系分析
这里使用的是用整形变量i来轮流来实现这个“轮流”过程
题目改为“每次随机地让一个吸烟者吸烟”,则可以使用random()函数来实现这个代码
若一个生产者要生产多种产品(或者说会引发多种前驱事件),那么各个V操作应该放在各自对应的“事件”发生之后的位置
问题描述:
有读者和写者两组并发进程,共享一个文件,当两个或两个以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。
因此要求:
读者进程与消费者进程不同,读者进程在读数据后并不会将数据清空,并不会改变数据。因此多个读者可同时访问共享数据
读进程与写进程同时共享数据,可能导致读出的数据不一致的问题
两个写进程同时共享数据,可能导致数据错误覆盖的问题
关系分析
两类进程:写进程、读进程
互斥关系:写进程-写进程、写进程-读进程。读进程与读进程不存在互斥问题
问题:只要有读进程还在读,写进程就要一直阻塞等待,可能“饿死”。因此,这种算法中,读进程是优先的
问题解决:
结论:在这种算法中,连续进入的多个读者可以同时读文件;写者和其他进程不能同时访问文件;写者不会饥饿,但也并不是真正的“写优先”,而是相对公平的先来先服务原则
所以也称“读写公平法”
读者-写者问题为我们解决复杂的互斥问题提供了一个参考思路
其核心思想在于设置了一个计数器count用来记录当前正在访问共享文件的读进程数。我们可以用count的值来判断当前进入的进程是否是第一个/最后一个读进程,从而做出不同的处理
问题描述:
关系分析:
系统中有5个哲学家进程,5位哲学家与左邻右舍对其中间筷子的访问是互斥关系
思路整理:
这个问题中只有互斥关系,但与之前遇到的问题不同的事,每个哲学家进程需要同时持有两个临界资源才能开始吃饭。如何避免临界资源分配不当造成的死锁现象,是哲学家问题的精髓
信号量设置:
定义互斥信号量数组chopsticks[5]={1,1,1,1,1}用于实现对5个筷子的互斥访问。并对哲学家按0~4编号,哲学家i左边的筷子编号为i,右边的筷子编号为(i+1)%5
方法一:至多只允许n-1位哲学家同时去拿左筷子,最终能保证至少有一位哲学家能进餐,并在用完后释放两只筷子供他人使用
方法二:仅当哲学家的左右手筷子都拿起来时才允许进餐
解法1:利用AND型信号量机制实现
多个临界资源,要么全部分配,要么一个都不分配,因此不会出现死锁的情况
解法2:利用信号量的保护机制实现
通过互斥信号量mutex对eat()之前取左侧和右侧筷子的操作进行保护,可以防止死锁的出现
方法三:规定奇数号哲学家先拿左筷子再拿右筷子,而偶数号哲学家相反
哲学家进餐问题的关键在于解决进餐死锁问题
这些进程之间只存在互斥关系,但是与之前接触到的互斥关系不同的是,每个进程都需要同时持有两个临界资源,因此就有“死锁”问题的隐患
管程的定义和基本特征
管程是一种特殊的软件模块,有这些部分组成:
可以把管程理解成一个类,过程可以理解为函数
管程的基本特征:
由编译器负责实现各进程互斥地进入管程中的过程
管程中设置条件变量和等待/唤醒操作,以解决同步问题
拓展:用管程解决生产者消费者问题
引入管程的目的无非就是要更方便地实现进程互斥和互斥