一、进程同步的基本概念
在多道程序环境下,进程是并发执行的。不同进程之间存在不同的相互制约关系。为了协调进程之间的相互制约关系,引入了进程同步的概念。For example:计算1+2*3,系统产生两个进程:一个是加法进程,一个是乘法进程。要使得计算正确,加法进程务必要在乘法进程之后,但是由于操作系统的异步性,若不加以制约,加法进程有可能发生在乘法进程之前,所以操作系统要以某种机制去约束加法进程,让其在乘法进程之后完成。
- 临界资源
虽然多个进程可以共享系统中的各种资源,但其中许多资源一次只能为一个进程所用,我们将一次仅允许一个进程使用的资源称为临界资源。如打印机等一些物理设备,车票数据等。
对临界资源的访问,必须互斥进行,在每个进程中,访问临界资源的那段代码称为临界区。为了保证临界资源的正确使用,可以把临界资源的访问分成四个部分:
(1)进入区:为了进入临界区使用临界资源,在进入区要检查是否可进入临界区,则应该设置正在访问临界区的标志,以阻止其他进程同时进入临界区。
(2)临界区
(3)退出区:将正在访问临界区的标志清除。
(4)剩余区:代码中的其余部分
do{
entrySection //进入区
criticalSection //临界区
exitSection //退出区
remainderSection //剩余区
} while(true)
-
同步
同步也就是直接制约关系,是指为了完成某种任务而建立的两个或多个进程,这些进程因需要在某些位置上协调它们的工作次序而等待、传递信息所产生的制约关系。进程间的制约关系源于它们之间的相互合作。
例如,输入进程A通过单缓冲向进程B提供数据。当缓冲区为空时,进程B不能获得所需数据而阻塞,一旦进程A将数据送入缓冲区,进程B就被唤醒。反之,当缓冲区满时,进程A被阻塞,当B取走缓冲区数据时候,才唤醒进程A。生产者-消费者模型就是这个的一个实例。 -
互斥
间接制约关系。当一个进程进入临界区使用临界资源时,另一个进程必须等待,当占用临界资源的进程退出临界区后,另一个进程才允许去访问此临界资源。卖票模型就是这个的一个实例。
为了禁止两个进程同时进入临界区,同步机制应该遵循以下准则:
(1)空闲让进:临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区。
(2)忙则等待:当已有进程进入临界区时,其他试图进入临界区的进程必须等待。
(3)有限等待:对请求访问的进程。应该保证能在有限时间内进入临界区。
(4)让权等待:当进程不能进入临界区时,立即释放处理器,防止进程忙等待。
二、实现临界区互斥的基本方法:
在进入区设置并检查一些标准来标明是否有进程在临界区中,若已有进程在临界区,则在进入区通过循环检查进行等待,进程离开临界区后则在退出区修改标志。
- 单标志法:
这个算法设置一个共用整型变量 turn,用于指示被允许进入临界区的进程编号,即若 turn = 0,允许P0进程进入临界区。这样可以确保每次就只允许一个进程进入临界区。但是要确保两个进程持续交替进入临界区。如果某进程不再进入临界区,则另一个进程也无法进去(违背了“空闲让进”的准则)。比如,P0从临界区顺利离开,此时临界区空闲,turn = 1,但是P1没有进入临界区的打算,此时turn = 1一直成立,P0也无法进入临界区。
伪代码描述如下:
P0进程:
while(turn!=0);//循环等待
criticalSection(); //进入临界区
turn = 1;
remainderSection(); //进入剩余区
/*===================================*/
P1进程:
while(turn!=1);//循环等待
criticalSection(); //进入临界区
turn = 0;
remainderSection(); //进入剩余区
- 双标志法先检查
这个算法的进本思想是在每个进程访问临界区资源之前,先查看临界资源是否正在被访问,若正被访问,则该进程需要等待;否则,进程才进入自己的临界区。为此,设置一个数据flag[i],如果第i个元素值为FALSE,表示Pi进程未进入临界区;值为TRUE,表示Pi进程进入临界区。
伪代码描述如下:
Pi进程:
while(flag[j]);//检查对方标志位,循环等待
flag[i] = TRUE;//设置标志位为TRUE
criticalSection();//进入临界区
flag[i] = FALSE;//退出临界区,设置标志位为FALSE
remainderSection();//进入剩余区
/*===========================*/
Pj进程:
while(flag[i]);//检查对方标志位,循环等待
flag[j] = TRUE;//设置标志位为TRUE
criticalSection();//进入临界区
flag[j] = FALSE;//退出临界区,设置标志位为FALSE
remainderSection();//进入剩余区
- 算法优点:不需要两个进程必须交替进入
- 算法缺点:可能出现Pi和Pj同时进入临界区的情况。在进程并行执行的情况下,按时间顺序,若flag[i],flag[j]一开始均为FALSE,执行了while(flag[j])紧接着执行了while(flag[i]),两个线程就都同时进入了临界区,违背“忙则等待”的原则。问题出在检查标志和设置标志不能一次性完成。
- 双标志法后检查:先将自己的标志设置为TRUE,再检测对方发状态标志,若对方标志为TRUE,则进程等待;否则进入临界区。
Pi进程:
flag[i] = TRUE;
while(flag[j]);//检查对方标志位,循环等待
criticalSection();//进入临界区
flag[i] = FALSE; //退出临界区
remainderSection();//进入剩余区
/*=============================*/
Pj进程:
flag[j] = TRUE;
while(flag[i]);//检查对方标志位,循环等待
criticalSection();//进入临界区
flag[i] = FALSE; //退出临界区
remainderSection();//进入剩余区
- 优点:避免了上一个算法的两个进程同时进入临界区的情况
- 缺点:当两个进程几乎同时都想进入临界区时,它们分别将自己的标志位设为TRUE,并且同时检测对方的状态,发现对方标志位都是TRUE,结果都不能进到临界区。
- 皮特森算法:为了防止两个进程进入临界区而无限期等待,又设置了一个变量turn,每个 进程在先设置自己的标志后再设置turn,这时,再同时检测另一个进程状态flag标志和turn标志,以便保证一个进程同时要求进入临界区时,只允许进入一个。
Pi进程:
flag[i] = TRUE;turn = j;
while(flag[j] && turn == j);
criticalSection();
flag[i] = FALSE;
remainderSection();
/*=============================*/
Pj进程:
flag[j] = TRUE;turn = i;
while(flag[i] && turn == i);
criticalSection();
flag[j] = FALSE;
remainderSection();
- 该算法很好解决了之前三个方法的不足之处。
- 中断屏蔽法
当一个进程正在使用处理机执行它的临界区代码时,防止其他进程进入临界区进行访问的最简单的方式就是,禁止一切中断发生(屏蔽中断,关中断)。因为CPU只在中断时引起进程切换,因此屏蔽中断能够保证当前运行的进程执行完临界区代码,进而保证互斥正确实现,然后执行开中断。
.
.
关中断;
执行临界区;
开中断;
.
.
- 这种方法限制了处理机交替执行程序的能力,因此执行效率会明显下降。对内核来说,在它指向更新变量或列表的几条指令期间,关中断很方便,但是将关中断权利交给用户则很不明智,如果一个进程关中断后不再开中断,系统可能终止。
- 硬件指令法
TestAndSet指令:这条指令是个原语。其功能是读出指定标志后把该标志设置为真。功能描述:
bool TestAndSet(bool *lock){
bool old;
old = *lock;
*lock = true
return old;
}
- 可以视为每个临界资源设置一个共享布尔变量lock,表示资源的两种状态:true表示正在被占用,false为初始值。在进程访问临界资源之前,利用TestAndSet方法检查和修改标志lock;若有进程在临界区,则重复检查,直到进程退出。利用该指令实现进程互斥算法描述如下:
while TestAndSet(&lock);
进程的临界区代码段
lock = false;
进程的其他代码;
- Swap指令:该指令的功能是交换两个字(节)的内容。
Swap(bool *a, bool *b){
bool temp;
temp = *a;
*a = *b;
*b = temp;
}
- PS:以上两个指令都是硬件直接实现,不是软件实现的定义,不会被中断!
- 硬件方法的优点:适用于任意数目的进程,而不管是单处理机还是多处理机;并且能简单容易验证其正确性。可以支持进程内有多个临界区,只需为每个临界区设立一个布尔变量。
- 硬件方法的缺点:进程等待进入临界区时要小号处理机时间,不能实现让权等待。从等待进程中选择一个进入临界区,有可能进程一直选不上,从而导致“饿死”。