前言
两个进程之间可以使独立的也可以是协作的,协作进程能与系统内的其他执行进程相互影响.协作进程或能直接共享逻辑地址空间,或能通过文件或消息来共享数据.而 共享数据的并发访问可能导致数据的不一致
1.临界区问题
1.临界资源
临界资源是一次仅允许一个进程使用的共享资源。各进程采取互斥的方式,实现共享的资源称作临界资源。属于临界资源的硬件有,打印机,磁带机等;软件有消息队列,变量,数组,缓冲区等。诸进程间采取互斥方式,实现对这种资源的共享。
2.临界区:
每个进程中访问临界资源的那段代码称为临界区(criticalsection),每次只允许一个进程进入临界区,进入后,不允许其他进程进入。不论是硬件临界资源还是软件临界资源,多个进程必须互斥的对它进行访问。多个进程涉及到同一个临界资源的的临界区称为相关临界区。使用临界区时,一般不允许其运行时间过长,只要运行在临界区的线程还没有离开,其他所有进入此临界区的线程都会被挂起而进入等待状态,并在一定程度上影响程序的运行性能。(查看原文)
临界区问题是设计一个协议以便协作进程
- 协作进程结构
在进入临界区前,每个进程应请求许可.实现请求的代码区称为进入区.临界区之后有退出区,其他代码为剩余区 - 临界区问题的解决方案应满足互斥,进步,有限等待
- 互斥:进程Pi在临界区内执行,其他进程不能在其临界区内执行
- 进步:如果没有进程在其临界区内执行,并且有进程需要进入临界区,那么只有那些不在剩余区内执行的进程可以参加选择,以便确定谁能下次进入临界区,而且这种选择不能无线推迟
- 有限等待:从一个进程做出进入临界区的请求直到这个请求允许为止,其他进程允许进入其临界区的次数具有上限
- 处理临界区的两种方法:抢占式内核和非抢占式内核
- 抢占式内核允许处于内核模式的进程被抢占
- 非抢占式内核不允许处于内核模式的进程被抢占,处于内核模式运行的进程会一直运行,直到退出内核模式,阻塞或自愿放弃CPU控制
2.硬件同步
基于加锁(locking)为前提,即通过锁来保护临界区.通过硬件指令来解决临界区问题
3.互斥锁
一个进程在进入临界区时应得到锁,在退出临界区时释放锁
伪代码
/*available为true,表示锁可用,进程能够进入临界区*/
acquire(){
while(!available){};//忙等待busy wait
available = false;
}
release(){
available = true;
}
忙等待:当一个进程在临界区中,任何其他进程在进入临界区时必须不断循环判断锁是否可用.这种互斥锁称为自旋锁,进程不停的旋转,以等待所变得可用
忙等待浪费CPU资源(CPU需要分配资源来不停的执行循环),优点是没有上下文切换(因为协作进程的代码是都在执行的)
4.信号量(semaphore)
一个信号量S是一个整型变量.除了初始化外,只能通过两个标准原子操作:wait()和signal()来访问
wait()称为P原语,用作测试signal()称为V原语,用作增加
伪代码
/*wait()*/
wait(S){
while(S <= 0){};//忙等待busy wait
S--;
}
/*signal()*/
signal(S){
S++;
}
-
操作系统通常区分计数信号量(其值可以使任意整数)与二进制信号量(S只能是0或者1,类似于互斥锁)
-
进程信号量可以用于控制访问具有多个实例的临界资源.信号量的初值为可用资源数量:
例子: 假设有3个协作进程(取名A B C)都需要使用打印机,打印机有2个. 信号量的初值S=2. A进入临界区时执行wait(),不满足循环条件,S--,S=1;获得打印机执行,可用打印机为1个 B进入临界区时执行wait(),不满足循环条件,S--,S=0;获得打印机执行,可用打印机为0个 C进入临界区时执行wait(),满足循环条件,一直在循环中,进行忙等待 若此时A使用完了打印机,会执行signal(),S++,S=1,可用打印机为1个,C跳出循环,执行S--操作;获得打印机执行,可用打印机为0个
当信号量的计数为0时,所有资源都在使用中,后面使用资源的进程将会阻塞,知道计数大于0
5.死锁与饥饿
假设有一个系统,有有两个进程p0和p1,两个进程都有共享信号量S和Q,初值均为1:
若p0和p1同时执行,p0执行wait(S),S=0;p1执行wait(Q),Q=0,现在问题就出来了,p0执行wait(Q),和p1执行wait(S),两个进程就会一直等在这里,这两个进程都在等待signal()事件发生,这个状态就称为死锁,导致了无限阻塞或饥饿
6.经典同步问题
1. 有界缓冲问题(生产者-消费者问题)
通过一组信号量,来控制协作进程之间的同步协调工作
伪代码
/*一组信号量*/
int n;//n表示有n个缓冲区//本例n=5
semaphore mutex = 1;//提供缓冲池访问的互斥要求
semaphore empty= n;//表示空的缓冲区的数量
semaphore full = 0;//表示已使用缓冲区的数量
/*生产者进程*/
do{
....
/*produce an item in nextproduced*/
....
wait(empty);//缓冲区是否有空余
wait(mutex);
....
/*add next produced to the buffer*/
....
signal(mutex);
signal(full);
}while(true)
/*消费者进程*/
do{
wait(full);//判断缓冲区是否有产品
wait(mutext);
....
/*remove an item from buffer to next consumed*/
....
signal(mutex);
signal(empty);
....
/*consume the item in next consumed*/
....
}while(true)
2.哲学家问题
如图,有五个进程P0-P4,每个进程之间有临界资源(如p0和p1之间有临界资源R0),进程Pi只有同时拥有Ri和R_(i+1)%5才能进入临界区
伪代码
do{
wait(R[i]);
wait(R[(i+1)%5]);
....
/*run for a while*/
....
signal(R[i]);
signal(R[(i+1)%5]);
....
}while(true)
若此时进程pi同时使用其Ri,这样就造成了五个进程都停在wait(R[(i+5)%5]),进而五个进程都执行不了,形成了死锁
死锁问题的补救措施:
- 最多允许4个进程同时执行
- 只用一个进程的两个临界资源都可用时,才能拥有执行权
- 使用非对称解决方案:单号的进程Pi先拿起Ri再拿起R_(i+1)%5,双号的进程Pi先拿起R_(i+1)%5再拿起Ri