实现临界区互斥的方法、同步与互斥的基本概念
有关同步和互斥基本概念
为什么要引入进程同步的概念?
在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系。为了协调相互制约关系,引入了进程同步的概念
临界资源
一次仅仅允许一个进程使用的资源被称之为临界资源
临界资源的种类:输入机(键盘)、打印机、磁带机等
临界区
进程中访问临界资源的那段代码,又称之为临界段
补充:当有线程进入临界区段时其他线程或者进程必须进入等待状态之中。
原子操作
原子操作指的是不会被线程调度机制打断的操作,且这种操作一旦执行,就会一直运行直到结束,中间不会有任何线程切换。
锁
保证线程持续运行的一种机制。
只有第一个申请到锁的进程才能够继续运行。
本质:由一位内存、一个阻塞队列和申请锁、释放锁的方法组成的数据结构
进程同步
并发的多个进程(线程)按照某种次序执行某种程序以合作完成某种功能的制约关系。
补充:同步也被称之为直接制约关系。
互斥
当一个进程进入临界区使用临界资源时,另外一个进程必须等待,当其退出时才能进入下一个进程。
死锁
死锁:多个进程全部阻塞,形成等待资源的循环链
饥饿
饥饿:一个就绪进程被调度程序长期忽视、不被调度执行;一个进程长期得不到资源
临界区的访问规则
为了禁止两个进程同时进入临界区,同步机制应该遵循以下基本规则
- 空闲让进:没有任何进程在临界区的时,允许进入
- 忙则等待 : 临界区存在进程的时候,其他进程不允许进入临界区
- 有限等待: 等待进入临界区的进程不能一直(无限)等待
- 让权等待: 不能进入临界区的进程,应立即释放cpu(如转换到阻塞状态)
临界区 互斥的基本方法——软件实现方法
单标志法
//P0进程
while(turn!=0); //进入区
critical section; //临界区
turn=1; //退出区
remainder section; //剩余区
//P1进程
while(turn!=1); //进入区
critical section; //临界区
turn=0; //退出区
remainder section; //剩余区
该方式设置⼀个公用的整型变量 turn ,用于指示被允许进入临界区的进程编号,若 turn=0 ,则允许 P0 进程进入临界区;若 turn=1 ,则允许 P1 进程进入临界区。 为什么要设置两个不同的turn量,即trun=0 or turn=1;
因为从上述代码可得,该方式只能⽀持两个进程交替轮流进⼊临界区。
考虑这样⼀个情形: 进程使⽤完临界资源,退出临界区后,此时 turn=1 ,但进程没有进⼊临界区的打算,那么 turn 变量永远不会变 为 0(因为只有进程使⽤临界资源后才会置 turn=0 ),这样 就无法再次进⼊临界区,然而此时临界区是空闲的,该方式违背了“空闲让进”的准则。
双标志先检查法
//Pi进程
while(flag[j]); //STEP1
flag[i]=TRUE; //STEP3
critical section;
flag[i]=FALSE;
remainder section
//Pj进程
while(flag[i]); //STEP2
flag[j]=TRUE; //STEP4
critical section;
flag[j]=FALSE;
remainder section;
单标志法的缺陷:
由于单标志法只能支持两个进程交替进⼊临界区,归根结底的原因就是只使用了 turn ⼀个变量,只能表示 P0 在临界区内或 P1 在临界区内两种状态,这种方式就隐含了这样⼀层意思:==P0 不在临界区内那 P1 ⼀定在临界区内; P1 不在临界区内那 P0 ⼀定在临界区内,==这就是单标志带来的局限性:可表示的状态过少。
那么双标志法就引⼊了多个标志位: flag 数组,其中 flag[i]=TRUE 就代表进 程 在临界区内,反之 flag[i]=FALSE 就代表进程 不在临界区内。
优点:不要求进程交替进入,单个进程可以连续使用临界区。
缺点: 按照 STEP1~4 的顺序执行时, Pi 和 Pj 进程可能会同时进入临界区,这样就违背了“忙则等待”
补充 : 造成缺点的原因
检测操作(while 语句)和修改操作(赋值语句)不是⼀次性完成的,即缺失了原⼦性。
双标志后检查法
//Pi进程
flag[i]=TRUE;
while(flag[j]);
critical section;
flag[i]=FALSE;
remainder section;
//Pj进程
flag[j]=TRUE;
while(flag[i]);
critical section;
flag[j]=FALSE;
remainder section;
种方式改变了双标志先检查法中检测操作和赋值操作的顺序,避免了两个进程同时进入临界区,然而会造成两个进程都不能进入临界区,违背了“空闲让进”的准则,造成“饥饿现象”。
Peterson算法
//Pi进程
flag[i]=TRUE;turn=j;
while(flag[j]&&turn==j);
critical section;
flag[i]=FALSE;
remainder section;
//Pj进程
flag[j]=TRUE;turn=i;
while(flag[i]&&turn==i);
critical section;
flag[j]=FALSE;
remainder section;
turn 在某⼀时刻的值肯定是确定的,让两个进程相互谦让,都将 turn 置为对方进程的编号,最后 turn 的值只能是其中⼀个,就代表其中⼀个进程的谦让是无效的,因此能够成功解决两个进程都不能进入临界区的尴尬局面。
优点:遵循了 空闲让进 忙则等待 有限等待三个准则,不符合让权等待
补充:此算法利用flag 数组解决临界资源的互斥访问,利用turn 变量解决“饥饿”现象。
临界区 互斥的基本方法——硬件实现方法
中断屏蔽法
方法: 进入临界区:禁用所有中断。
离开临界区:使能所有中断。
缺点:
- 禁用中断后,进程无法被停止。整个系统都会为此停下来,其它进程可能进入饥饿状态。
- 临界区可能很长,此间全部禁用中断可能会出现意料之外的问题。
- 不适合多处理机,只适合操作系统内核进程,不适合于用户进程。
硬件指令方法——TestAndSet指令
boolean TestAndSet(boolean *lock){
boolean old;
old=*lock;
*lock=TRUE;
return old;
}
为每个临界资源设置⼀个布尔变量 lock ,表⽰该资源的两种状态:TRUE ⽰正在被占⽤;FALSE 表⽰当前没有被占⽤,初值为 FALSE。利⽤ TestAndSet 指令实现互斥的过程描述如下:
while(TestAndSet(&lock));
临界区代码段;
lock=FALSE;
其他代码;
优点:适用于多处理器系统 ,变成了原子操作
缺点: 暂时无法进入临界区的进程会占用cpu循环执行ts指令,因此不能实现“让权等待”
硬件指令方法——Swap指令
void Swap(boolean *a, boolean *b){
boolean tmp;
tmp=*a;
*a=*b;
*b=tmp;
}
利用Swap指令实现互斥的过程描述如下,其中 key 时进程的局部布尔变量, lock 是每⼀个临界资源的共享布尔变量:
key=TRUE;
while(key!=FALSE){
Swap(&lock, &key);
}
临界区代码段;
lock=FALSE;
其他代码;