进程间通信—如何避免竞争条件
竞争条件:有两个或多个进程读写某些共享数据,而最后的结果取决于进程运行的精确时序,称为竞争条件(race condition)。
进程间通信的三个问题:
- 如何传递?
- 如何确保两个或更多的进程在关键的活动中不会出现交叉?
- 如何确保进程按正确的顺序?
这三个问题中的两个问题对于线程来说是同样适用的,第一个问题(即传递信息)对线程而言比较容易,因为他们共享一个地址空间(在不同地址空间需要通信的线程属于不同进程之间通信的情形)。但是另外两个问题(需要梳理清楚并保持恰当的顺序)同样适用于线程。同样的问题可用同样的方法解决。
如何避免竞争条件
要避免这种错误,关键是要找出某种途径来阻止多个进程同时读写共享的数据。换言之,需要的是互斥(mutual exclusion),即以某种手段确保当一个进程在使用一个共享变量或文件时,其他进程不能做同样的操作。
使用一种更抽象的方式来竞争条件。一个进程的一部分时间做内部计算或另外一些不会引发竞争条件的操作。把对共享内存进行访问的程序片段称作临界区域(critical region)或临界区(critical section)。
对于一个好的避免竞争条件的解决方案。 需要满足以下四个条件:
- 任何两个进程不能同时处于临界区
- 不应对CPU速度和数量做任何假设
- 临界区外运行的进程不得阻塞其他进程
- 不得使进程无限期等待进入临界区
忙等待的互斥方案
1.屏蔽中断
CPU只有发生时钟中断或其他中断时才会进行进程切换。在单处理系统中,最简单的方法就是在进程在刚刚进入临界区后立即屏蔽所有中断,并在离开前打开中断。屏蔽中断后CPU将不会切换到其他进程。于是,一旦某个进程屏蔽中断之后,就可以检查和修改共享内存,而不必担心其他进程介入。
这个方案并不好①把屏蔽中断的权利交给用户进程是不明智的,若一个进程屏蔽中断后不再打开中断,整个系统可能会因此终止。②如果系统是多处理器,则屏蔽中断仅仅对执行屏蔽中断指令的那个CPU有效。其他CPU可以访问共享内存。
屏蔽中断对于操作系统本身而言是一项很有用的技术,但是对于用户进程则不是一种合适的通用互斥机制。如今多核芯片数量的数量越来越多,因此屏蔽中断来达到互斥的可能性—甚至在内核中—变得日益减少。
2. 锁变量
设想有一个共享锁变量,其初始值为0。当一个进程想进入其临界区,它首先测试这把锁。如果该锁的值为0,则该进程将其设置为1并进入临界区。若这把锁值已经为1,则该进程将等待直到其值为0。
这个方法不能实现互斥。试想,一个进程读出锁变量的值并发现它为0,而在它将锁变量值设置为1之前,另一个进程被调度运行(此时锁边量仍为0),于是便有两个进程同时进入了临界区。
3. 严格轮换法
如下代码所示,整型变量turn,初始值为0,用于记录轮到哪个进程进入临界区,并检查或更新共享内存。开始时,进程0检查turn,其值为0,于是进入临界区。进程1发现其值为0,则其在循环中不停检测turn,看其值何时变为1。
连续测试一个变量直到某个值出现为止吗,称为忙等待(busy waiting)。(这种方式浪费CPU时间,因此通常需要避免,只有认为等待时间是非常短的情形下,